From 5a72479951211013a3a2e9e410e7fc2c8cef4934 Mon Sep 17 00:00:00 2001 From: vkukhtyn <37999482+vkukhtyn@users.noreply.github.com> Date: Wed, 10 Mar 2021 14:56:25 +0200 Subject: [PATCH 001/127] [WIP] SNMP initial implementation (#3755) * Add SNMP type to transport profiles * Update reference config for transport profiles * Initial implementation to retrieve a value using SNMP GET message * Fix license header * Move config files * Use async handler for SNMP response * Send SNMP agent response to attributes and telemetry * UI: add SNMP option for device profile * UI: use JSON field to set SNMP profile configuration * Handle device profile update event * Use concurrent hash map for SNMP device sessions * UI: Add device transport configuration control * Cancel async request to avoid memory leak and timeout handling * Start SNMP pooling after application started * Move OID per profile mapping to SNMP transport context * Fix build after merge with 3.2.1-SNAPSHOT * Init device sessions on TB start * Fix build error, refactoring * Update session context on device update * Set device info on session context creating * Refresh pooling params on device or profile update * Update license header * Process device and profile transport config update * Process SNMP response asynchronously * Change polling implementation --- application/pom.xml | 4 + .../src/main/resources/thingsboard.yml | 2 + common/data/pom.xml | 4 + .../common/data/DeviceTransportType.java | 3 +- .../JsonBasedTransportConfiguration.java | 39 ++++ .../data/DeviceTransportConfiguration.java | 3 +- .../SnmpDeviceTransportConfiguration.java | 43 ++++ .../DeviceProfileTransportConfiguration.java | 8 +- .../profile/SnmpDeviceProfileKvMapping.java | 28 +++ .../SnmpProfileTransportConfiguration.java | 44 ++++ common/transport/pom.xml | 1 + common/transport/snmp/pom.xml | 66 ++++++ .../transport/snmp/SnmpTransportContext.java | 142 ++++++++++++ .../transport/snmp/SnmpTransportService.java | 147 +++++++++++++ .../snmp/session/DeviceSessionCtx.java | 206 ++++++++++++++++++ .../snmp/session/SnmpSessionListener.java | 173 +++++++++++++++ .../server/dao/device/DeviceServiceImpl.java | 3 + msa/transport/pom.xml | 1 + msa/transport/snmp/docker/Dockerfile | 33 +++ .../snmp/docker/start-tb-snmp-transport.sh | 33 +++ msa/transport/snmp/pom.xml | 190 ++++++++++++++++ pom.xml | 11 + transport/pom.xml | 1 + transport/snmp/pom.xml | 112 ++++++++++ .../ThingsboardSnmpTransportApplication.java | 48 ++++ .../main/resources/device-data-config.json | 9 + .../main/resources/device-profile-config.json | 21 ++ .../home/components/home-components.module.ts | 3 + ...ile-transport-configuration.component.html | 6 + ...ile-transport-configuration.component.html | 23 ++ ...ofile-transport-configuration.component.ts | 100 +++++++++ ...ice-transport-configuration.component.html | 6 + ...ice-transport-configuration.component.html | 24 ++ ...evice-transport-configuration.component.ts | 100 +++++++++ .../home/pages/device/device.module.ts | 2 + ui-ngx/src/app/shared/models/device.models.ts | 42 +++- .../assets/locale/locale.constant-en_US.json | 9 + 37 files changed, 1681 insertions(+), 9 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/JsonBasedTransportConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java create mode 100644 common/transport/snmp/pom.xml create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportService.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionCtx.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/SnmpSessionListener.java create mode 100644 msa/transport/snmp/docker/Dockerfile create mode 100644 msa/transport/snmp/docker/start-tb-snmp-transport.sh create mode 100644 msa/transport/snmp/pom.xml create mode 100644 transport/snmp/pom.xml create mode 100644 transport/snmp/src/main/java/org/thingsboard/server/snmp/ThingsboardSnmpTransportApplication.java create mode 100644 transport/snmp/src/main/resources/device-data-config.json create mode 100644 transport/snmp/src/main/resources/device-profile-config.json create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.ts diff --git a/application/pom.xml b/application/pom.xml index c1a49b09e3..e3dcd9f0f9 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -85,6 +85,10 @@ org.thingsboard.common.transport coap + + org.thingsboard.common.transport + snmp + org.thingsboard dao diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 0cc7c08669..437a672c54 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -566,6 +566,8 @@ transport: bind_address: "${COAP_BIND_ADDRESS:0.0.0.0}" bind_port: "${COAP_BIND_PORT:5683}" timeout: "${COAP_TIMEOUT:10000}" + snmp: + enabled: "${SNMP_ENABLED:true}" swagger: api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" diff --git a/common/data/pom.xml b/common/data/pom.xml index 4454e628c0..bc977b1c52 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -79,6 +79,10 @@ org.thingsboard protobuf-dynamic + + org.apache.commons + commons-lang3 + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java index 9b9a021fcf..579cdde968 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceTransportType.java @@ -18,5 +18,6 @@ package org.thingsboard.server.common.data; public enum DeviceTransportType { DEFAULT, MQTT, - LWM2M + LWM2M, + SNMP } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/JsonBasedTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/JsonBasedTransportConfiguration.java new file mode 100644 index 0000000000..c65e9dd972 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/JsonBasedTransportConfiguration.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2021 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; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import java.util.HashMap; +import java.util.Map; + +public abstract class JsonBasedTransportConfiguration { + + @JsonIgnore + private final Map properties = new HashMap<>(); + + @JsonAnyGetter + public Map properties() { + return this.properties; + } + + @JsonAnySetter + public void put(String name, Object value) { + this.properties.put(name, value); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java index 5409dcd339..0ae9f26cf3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java @@ -29,7 +29,8 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes({ @JsonSubTypes.Type(value = DefaultDeviceTransportConfiguration.class, name = "DEFAULT"), @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"), - @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M")}) + @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"), + @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")}) public interface DeviceTransportConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java new file mode 100644 index 0000000000..3db31b4f2a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2021 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.data; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.DeviceTransportType; + +@Data +public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration { + + private String address; + private int port; + private String community; + private String protocolVersion; + + @Override + public DeviceTransportType getType() { + return DeviceTransportType.SNMP; + } + + @JsonIgnore + public boolean isValid() { + return StringUtils.isNotEmpty(this.address) + && this.port > 0 + && StringUtils.isNotEmpty(this.community) + && StringUtils.isNotEmpty(this.protocolVersion); + } +} 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 2feba45d34..ffbe5f7048 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 @@ -19,7 +19,6 @@ 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.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; @JsonIgnoreProperties(ignoreUnknown = true) @@ -28,9 +27,10 @@ import org.thingsboard.server.common.data.DeviceTransportType; include = JsonTypeInfo.As.PROPERTY, property = "type") @JsonSubTypes({ - @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), - @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), - @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M")}) + @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), + @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), + @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"), + @JsonSubTypes.Type(value = SnmpProfileTransportConfiguration.class, name = "SNMP")}) public interface DeviceProfileTransportConfiguration { @JsonIgnore diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java new file mode 100644 index 0000000000..6eff7cbd34 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2021 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 lombok.Data; +import org.thingsboard.server.common.data.kv.DataType; + +//TODO: rename class +@Data +public class SnmpDeviceProfileKvMapping { + private String key; + private DataType type; + private String method; + private String oid; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java new file mode 100644 index 0000000000..a2529e74e9 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2021 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 lombok.Data; +import org.thingsboard.server.common.data.DeviceTransportType; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@Data +public class SnmpProfileTransportConfiguration implements DeviceProfileTransportConfiguration { + + private int poolPeriodMs; + private int timeoutMs; + private int retries; + private List attributes; + private List telemetry; + + @Override + public DeviceTransportType getType() { + return DeviceTransportType.SNMP; + } + + @JsonIgnore + public List getKvMappings() { + return Stream.concat(attributes.stream(), telemetry.stream()).collect(Collectors.toList()); + } +} diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 957b0d518c..35722cdd1d 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -39,6 +39,7 @@ mqtt http coap + snmp diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml new file mode 100644 index 0000000000..1f334b9699 --- /dev/null +++ b/common/transport/snmp/pom.xml @@ -0,0 +1,66 @@ + + + 4.0.0 + + + org.thingsboard.common + 3.2.1-SNAPSHOT + transport + + + org.thingsboard.common.transport + snmp + jar + + Thingsboard SNMP Transport Common + https://thingsboard.io + + + UTF-8 + ${basedir}/../../.. + + + + + org.thingsboard.common.transport + transport-api + + + org.springframework + spring-context-support + + + org.springframework + spring-context + + + org.slf4j + slf4j-api + + + org.snmp4j + snmp4j + + + org.thingsboard.common + dao-api + + + diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java new file mode 100644 index 0000000000..66f852a209 --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -0,0 +1,142 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.snmp4j.PDU; +import org.snmp4j.Snmp; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.VariableBinding; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileKvMapping; +import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.dao.device.DeviceCredentialsService; +import org.thingsboard.server.transport.snmp.session.DeviceSessionCtx; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; + +@Service("SnmpTransportContext") +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')") +@Slf4j +public class SnmpTransportContext extends TransportContext { + @Autowired + DeviceCredentialsService deviceCredentialsService; + + @Autowired + SnmpTransportService snmpTransportService; + + @Getter + private final Map profileTransportConfig = new ConcurrentHashMap<>(); + @Getter + private final Map> pdusPerProfile = new ConcurrentHashMap<>(); + @Getter + private final Map deviceSessions = new ConcurrentHashMap<>(); + + public Optional findAttributesMapping(DeviceProfileId deviceProfileId, OID responseOid) { + if (profileTransportConfig.containsKey(deviceProfileId)) { + return findMapping(responseOid, profileTransportConfig.get(deviceProfileId).getAttributes()); + } + return Optional.empty(); + } + + public Optional findTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { + if (profileTransportConfig.containsKey(deviceProfileId)) { + return findMapping(responseOid, profileTransportConfig.get(deviceProfileId).getTelemetry()); + } + return Optional.empty(); + } + + private Optional findMapping(OID responseOid, List mappings) { + return mappings.stream() + .filter(kvMapping -> new OID(kvMapping.getOid()).equals(responseOid)) + //TODO: OID shouldn't be duplicated in the config, add backend and UI verification + .findFirst(); + } + + public void initPduListPerProfile() { + profileTransportConfig.forEach(this::updatePduListPerProfile); + } + + public void updatePduListPerProfile(DeviceProfileId id, SnmpProfileTransportConfiguration config) { + pdusPerProfile.put(id, createPduList(config)); + } + + public void updateDeviceSessionCtx(Device device, DeviceProfile deviceProfile, Snmp snmp) { + DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); + if (DeviceCredentialsType.ACCESS_TOKEN.equals(credentials.getCredentialsType())) { + SnmpDeviceTransportConfiguration snmpDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); + if (snmpDeviceTransportConfiguration.isValid()) { + DeviceSessionCtx deviceSessionCtx = new DeviceSessionCtx(this, credentials.getCredentialsId(), snmpDeviceTransportConfiguration, snmp, device.getId(), deviceProfile); + deviceSessionCtx.createSessionInfo(ctx -> getTransportService().registerAsyncSession(deviceSessionCtx.getSessionInfo(), deviceSessionCtx)); + this.deviceSessions.put(device.getId(), deviceSessionCtx); + } + } else { + log.warn("[{}] Expected credentials type is {} but found {}", device.getId(), DeviceCredentialsType.ACCESS_TOKEN, credentials.getCredentialsType()); + } + } + + public ExecutorService getSnmpCallbackExecutor() { + return snmpTransportService.getSnmpCallbackExecutor(); + } + + private List createPduList(SnmpProfileTransportConfiguration deviceProfileConfig) { + Map> varBindingPerMethod = new HashMap<>(); + + deviceProfileConfig.getKvMappings().forEach(mapping -> varBindingPerMethod + .computeIfAbsent(mapping.getMethod(), v -> new ArrayList<>()) + .add(new VariableBinding(new OID(mapping.getOid())))); + + return varBindingPerMethod.keySet().stream() + .map(method -> { + PDU request = new PDU(); + request.setType(getSnmpMethod(method)); + request.addAll(varBindingPerMethod.get(method)); + return request; + }) + .collect(Collectors.toList()); + } + + //TODO: Extract SNMP methods to enum + private int getSnmpMethod(String configMethod) { + switch (configMethod) { + case "get": + return PDU.GET; + case "getNext": + case "response": + case "set": + default: + return -1; + } + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportService.java new file mode 100644 index 0000000000..5d8e4d84b4 --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportService.java @@ -0,0 +1,147 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.snmp4j.Snmp; +import org.snmp4j.transport.DefaultUdpTransportMapping; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageDataIterable; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.transport.snmp.session.DeviceSessionCtx; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +@Service("SnmpTransportService") +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')") +@Slf4j +public class SnmpTransportService { + + private static final int ENTITY_PACK_LIMIT = 1024; + + @Autowired + private SnmpTransportContext snmpTransportContext; + + @Autowired + DeviceProfileService deviceProfileService; + + @Autowired + TenantService tenantService; + + @Autowired + DeviceService deviceService; + + @Getter + private ExecutorService snmpCallbackExecutor; + private Snmp snmp; + private ScheduledExecutorService pollingExecutor; + + @PostConstruct + public void init() { + log.info("Starting SNMP transport..."); + pollingExecutor = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("snmp-polling")); + //TODO: Set parallelism value in the config + snmpCallbackExecutor = Executors.newWorkStealingPool(20); + initializeSnmp(); + log.info("SNMP transport started!"); + } + + @PreDestroy + public void shutdown() { + log.info("Stopping SNMP transport!"); + if (pollingExecutor != null) { + pollingExecutor.shutdownNow(); + } + if (snmpCallbackExecutor != null) { + snmpCallbackExecutor.shutdownNow(); + } + if (snmp != null) { + try { + snmp.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + log.info("SNMP transport stopped!"); + } + + @EventListener(ApplicationReadyEvent.class) + @Order(value = 2) + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + log.info("Received application ready event. Starting SNMP polling."); + initSessionCtxList(); + startPolling(); + } + + private void initializeSnmp() { + try { + this.snmp = new Snmp(new DefaultUdpTransportMapping()); + this.snmp.listen(); + } catch (IOException e) { + //TODO: what should be done if transport wasn't initialized? + log.error(e.getMessage(), e); + } + } + + private void initSessionCtxList() { + //TODO: This approach works for monolith, in cluster the same data will be fetched by each node. + for (Tenant tenant : new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT)) { + TenantId tenantId = tenant.getTenantId(); + for (DeviceProfile deviceProfile : new PageDataIterable<>(pageLink -> deviceProfileService.findDeviceProfiles(tenantId, pageLink), ENTITY_PACK_LIMIT)) { + if (DeviceTransportType.SNMP.equals(deviceProfile.getTransportType())) { + snmpTransportContext.getProfileTransportConfig().put(deviceProfile.getId(), + (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()); + initDeviceSessions(deviceProfile); + } + } + } + snmpTransportContext.initPduListPerProfile(); + } + + private void initDeviceSessions(DeviceProfile deviceProfile) { + for (DeviceInfo deviceInfo : new PageDataIterable<>(pageLink -> deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(deviceProfile.getTenantId(), deviceProfile.getId(), pageLink), ENTITY_PACK_LIMIT)) { + snmpTransportContext.updateDeviceSessionCtx(deviceInfo, deviceProfile, snmp); + } + } + + private void startPolling() { + //TODO: Get poll period from configuration; + int poolPeriodSeconds = 1; + pollingExecutor.scheduleAtFixedRate(() -> snmpTransportContext.getDeviceSessions().values().forEach(DeviceSessionCtx::executeSnmpRequest), + 0, poolPeriodSeconds, TimeUnit.SECONDS); + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionCtx.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionCtx.java new file mode 100644 index 0000000000..424b964387 --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionCtx.java @@ -0,0 +1,206 @@ +/** + * Copyright © 2016-2021 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.snmp.session; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.snmp4j.CommunityTarget; +import org.snmp4j.Snmp; +import org.snmp4j.Target; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.OctetString; +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.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.SessionInfoCreator; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; +import org.thingsboard.server.transport.snmp.SnmpTransportContext; + +import java.io.IOException; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +@Slf4j +public class DeviceSessionCtx extends DeviceAwareSessionContext implements SessionMsgListener { + private final AtomicInteger msgIdSeq = new AtomicInteger(0); + + @Getter + @Setter + private SnmpDeviceTransportConfiguration deviceTransportConfig; + @Getter + @Setter + private SnmpSessionListener snmpSessionListener; + @Getter + @Setter + private Target target; + @Getter + @Setter + private volatile TransportProtos.SessionInfoProto sessionInfo; + + private Snmp snmp; + private SnmpProfileTransportConfiguration snmpProfileTransportConfiguration; + private long previousRequestExecutedAt = 0; + + public DeviceSessionCtx(SnmpTransportContext transportContext, String token, SnmpDeviceTransportConfiguration deviceTransportConfig, + Snmp snmp, DeviceId deviceId, DeviceProfile deviceProfile) { + super(UUID.randomUUID()); + this.snmpSessionListener = new SnmpSessionListener(transportContext, token); + super.setDeviceId(deviceId); + super.setDeviceProfile(deviceProfile); + //TODO: What should be done if snmp null? + if (snmp != null) { + this.snmp = snmp; + } + this.snmpProfileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + initTarget(this.snmpProfileTransportConfiguration, deviceTransportConfig); + } + + @Override + public int nextMsgId() { + return msgIdSeq.incrementAndGet(); + } + + @Override + public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { + } + + @Override + public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { + } + + @Override + public void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification) { + } + + @Override + public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { + } + + @Override + public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { + } + + @Override + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) { + super.onDeviceProfileUpdate(sessionInfo, deviceProfile); + if (DeviceTransportType.SNMP.equals(deviceProfile.getTransportType())) { + snmpProfileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + snmpSessionListener.getSnmpTransportContext().getProfileTransportConfig().put( + deviceProfile.getId(), + snmpProfileTransportConfiguration); + snmpSessionListener.getSnmpTransportContext().updatePduListPerProfile(deviceProfile.getId(), snmpProfileTransportConfiguration); + } else { + //TODO: should the context be removed from the map? + } + } + + @Override + public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { + super.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); + if (super.getDeviceProfile() != null && DeviceTransportType.SNMP.equals(super.getDeviceProfile().getTransportType())) { + snmpSessionListener.getSnmpTransportContext().updateDeviceSessionCtx(device, deviceProfile, null); + SnmpProfileTransportConfiguration profileTransportConfig = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + SnmpDeviceTransportConfiguration deviceTransportConfig = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); + initTarget(profileTransportConfig, deviceTransportConfig); + } else { + //TODO: should the context be removed from the map? + } + } + + public void createSessionInfo(Consumer registerSession) { + getSnmpSessionListener().getSnmpTransportContext().getTransportService().process(DeviceTransportType.SNMP, + TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(getSnmpSessionListener().getToken()).build(), + new TransportServiceCallback() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (msg.hasDeviceInfo()) { + sessionInfo = SessionInfoCreator.create(msg, getSnmpSessionListener().getSnmpTransportContext(), UUID.randomUUID()); + registerSession.accept(sessionInfo); + setDeviceInfo(msg.getDeviceInfo()); + } else { + log.warn("[{}] Failed to process device auth", getDeviceId()); + } + } + + @Override + public void onError(Throwable e) { + log.warn("[{}] Failed to process device auth", getDeviceId(), e); + } + }); + } + + public void executeSnmpRequest() { + long timeNow = System.currentTimeMillis(); + long nextRequestExecutionTime = previousRequestExecutedAt + snmpProfileTransportConfiguration.getPoolPeriodMs(); + if (nextRequestExecutionTime < timeNow) { + previousRequestExecutedAt = timeNow; + + snmpSessionListener.getSnmpTransportContext().getPdusPerProfile().get(deviceProfile.getId()).forEach(pdu -> { + try { + log.debug("[{}] Sending SNMP message...", pdu.getRequestID()); + snmp.send(pdu, + target, + deviceProfile.getId(), + snmpSessionListener); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + }); + } + } + + private void initTarget(SnmpProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + this.deviceTransportConfig = deviceTransportConfig; + CommunityTarget communityTarget = new CommunityTarget(); + communityTarget.setAddress(GenericAddress.parse(GenericAddress.TYPE_UDP + ":" + this.deviceTransportConfig.getAddress() + "/" + this.deviceTransportConfig.getPort())); + communityTarget.setVersion(getSnmpVersion(this.deviceTransportConfig.getProtocolVersion())); + communityTarget.setCommunity(new OctetString(this.deviceTransportConfig.getCommunity())); + communityTarget.setTimeout(profileTransportConfig.getTimeoutMs()); + communityTarget.setRetries(profileTransportConfig.getRetries()); + this.target = communityTarget; + log.info("SNMP target initialized: {}", this.target); + } + + //TODO: replace with enum, wtih preliminary discussion of type version in config (string or integer) + private int getSnmpVersion(String configSnmpVersion) { + switch (configSnmpVersion) { + case ("v1"): + return SnmpConstants.version1; + case ("v2c"): + return SnmpConstants.version2c; + case ("v3"): + return SnmpConstants.version3; + default: + return -1; + } + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/SnmpSessionListener.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/SnmpSessionListener.java new file mode 100644 index 0000000000..35aa1a9347 --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/SnmpSessionListener.java @@ -0,0 +1,173 @@ +/** + * Copyright © 2016-2021 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.snmp.session; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.JsonSyntaxException; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.snmp4j.PDU; +import org.snmp4j.Snmp; +import org.snmp4j.event.ResponseEvent; +import org.snmp4j.event.ResponseListener; +import org.snmp4j.smi.VariableBinding; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.adaptor.AdaptorException; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.common.transport.auth.SessionInfoCreator; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.snmp.SnmpTransportContext; + +import java.util.UUID; +import java.util.function.Consumer; + +@Slf4j +@AllArgsConstructor +public class SnmpSessionListener implements ResponseListener { + + @Getter + private final SnmpTransportContext snmpTransportContext; + + @Getter + private final String token; + + @Override + public void onResponse(ResponseEvent event) { + ((Snmp) event.getSource()).cancel(event.getRequest(), this); + snmpTransportContext.getSnmpCallbackExecutor().submit(() -> processSnmpResponse(event)); + } + + private void processSnmpResponse(ResponseEvent event) { + PDU response = event.getResponse(); + if (event.getError() != null) { + log.warn("Response error: {}", event.getError().getMessage(), event.getError()); + } + + if (response != null) { + log.debug("[{}] Processing SNMP response: {}", response.getRequestID(), response); + + DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); + TransportService transportService = snmpTransportContext.getTransportService(); + for (int i = 0; i < response.size(); i++) { + VariableBinding vb = response.get(i); + snmpTransportContext.findAttributesMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, + TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(token).build(), + new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { + try { + transportService.process(sessionInfo, + convertToPostAttributes(kvMapping.getKey(), kvMapping.getType(), vb.toValueString()), + TransportServiceCallback.EMPTY); + reportActivity(sessionInfo); + } catch (Exception e) { + log.warn("Failed to process SNMP response: {}", e.getMessage(), e); + } + }))); + snmpTransportContext.findTelemetryMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, + TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(token).build(), + new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { + try { + transportService.process(sessionInfo, + convertToPostTelemetry(kvMapping.getKey(), kvMapping.getType(), vb.toValueString()), + TransportServiceCallback.EMPTY); + reportActivity(sessionInfo); + + } catch (Exception e) { + log.warn("Failed to process SNMP response: {}", e.getMessage(), e); + } + }))); + } + } else { + log.warn("No SNMP response, requestId: {}", event.getRequest().getRequestID()); + } + } + + private TransportProtos.PostAttributeMsg convertToPostAttributes(String keyName, DataType dataType, String payload) throws AdaptorException { + try { + return JsonConverter.convertToAttributesProto(getKvJson(keyName, dataType, payload)); + } catch (IllegalStateException | JsonSyntaxException ex) { + //TODO: change the exception type + throw new AdaptorException(ex); + } + } + + private TransportProtos.PostTelemetryMsg convertToPostTelemetry(String keyName, DataType dataType, String payload) throws AdaptorException { + try { + return JsonConverter.convertToTelemetryProto(getKvJson(keyName, dataType, payload)); + } catch (IllegalStateException | JsonSyntaxException ex) { + //TODO: change the exception type + throw new AdaptorException(ex); + } + } + + private JsonElement getKvJson(String keyName, DataType dataType, String payload) throws AdaptorException { + JsonObject result = new JsonObject(); + switch (dataType) { + case LONG: + result.addProperty(keyName, Long.parseLong(payload)); + break; + case BOOLEAN: + result.addProperty(keyName, Boolean.parseBoolean(payload)); + break; + case DOUBLE: + result.addProperty(keyName, Double.parseDouble(payload)); + break; + case STRING: + result.addProperty(keyName, payload); + break; + default: + //TODO: change the exception type + throw new AdaptorException("Unsupported data type"); + } + return new JsonParser().parse(result.toString()); + } + + private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { + snmpTransportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(false) + .setRpcSubscription(false) + .setLastActivityTime(System.currentTimeMillis()) + .build(), TransportServiceCallback.EMPTY); + } + + @AllArgsConstructor + private static class DeviceAuthCallback implements TransportServiceCallback { + private final TransportContext transportContext; + private final Consumer onSuccess; + + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (msg.hasDeviceInfo()) { + onSuccess.accept(SessionInfoCreator.create(msg, transportContext, UUID.randomUUID())); + } else { + log.warn("Failed to process device auth"); + } + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process device auth", e); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index cd161d650e..c088d18d3c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConf import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -240,6 +241,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe case LWM2M: deviceData.setTransportConfiguration(new Lwm2mDeviceTransportConfiguration()); break; + case SNMP: + deviceData.setTransportConfiguration(new SnmpDeviceTransportConfiguration()); } } return deviceData; diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 6fdedfbfcf..6a9fe17fc2 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -38,6 +38,7 @@ mqtt http coap + snmp diff --git a/msa/transport/snmp/docker/Dockerfile b/msa/transport/snmp/docker/Dockerfile new file mode 100644 index 0000000000..e39624d538 --- /dev/null +++ b/msa/transport/snmp/docker/Dockerfile @@ -0,0 +1,33 @@ +# +# Copyright © 2016-2021 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. +# + +FROM thingsboard/openjdk8 + +COPY start-tb-snmp-transport.sh ${pkg.name}.deb /tmp/ + +RUN chmod a+x /tmp/*.sh \ + && mv /tmp/start-tb-snmp-transport.sh /usr/bin + +RUN yes | dpkg -i /tmp/${pkg.name}.deb +RUN rm /tmp/${pkg.name}.deb + +RUN systemctl --no-reload disable --now ${pkg.name}.service > /dev/null 2>&1 || : + +RUN chmod 555 ${pkg.installFolder}/bin/${pkg.name}.jar + +USER ${pkg.user} + +CMD ["start-tb-snmp-transport.sh"] diff --git a/msa/transport/snmp/docker/start-tb-snmp-transport.sh b/msa/transport/snmp/docker/start-tb-snmp-transport.sh new file mode 100644 index 0000000000..2e69cf26f9 --- /dev/null +++ b/msa/transport/snmp/docker/start-tb-snmp-transport.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright © 2016-2021 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. +# + +CONF_FOLDER="/config" +jarfile=${pkg.installFolder}/bin/${pkg.name}.jar +configfile=${pkg.name}.conf + +source "${CONF_FOLDER}/${configfile}" + +export LOADER_PATH=/config,${LOADER_PATH} + +echo "Starting '${project.name}' ..." + +cd ${pkg.installFolder}/bin + +exec java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.snmp.ThingsboardSnmpTransportApplication \ + -Dspring.jpa.hibernate.ddl-auto=none \ + -Dlogging.config=/config/logback.xml \ + org.springframework.boot.loader.PropertiesLauncher diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml new file mode 100644 index 0000000000..df5caa658c --- /dev/null +++ b/msa/transport/snmp/pom.xml @@ -0,0 +1,190 @@ + + + 4.0.0 + + org.thingsboard.msa + transport + 3.2.1-SNAPSHOT + + + org.thingsboard.msa.transport + snmp + pom + + ThingsBoard SNMP Transport Microservice + https://thingsboard.io + ThingsBoard SNMP Transport Microservice + + + UTF-8 + ${basedir}/../../.. + tb-snmp-transport + tb-snmp-transport + /var/log/${pkg.name} + /usr/share/${pkg.name} + + + + + org.thingsboard.transport + snmp + ${project.version} + deb + deb + provided + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-tb-snmp-transport-deb + package + + copy + + + + + org.thingsboard.transport + snmp + deb + deb + ${pkg.name}.deb + ${project.build.directory} + + + + + + + + org.apache.maven.plugins + maven-resources-plugin + + + copy-docker-config + process-resources + + copy-resources + + + ${project.build.directory} + + + docker + true + + + + + + + + com.spotify + dockerfile-maven-plugin + + + build-docker-image + pre-integration-test + + build + + + ${dockerfile.skip} + ${docker.repo}/${docker.name} + true + false + ${project.build.directory} + + + + tag-docker-image + pre-integration-test + + tag + + + ${dockerfile.skip} + ${docker.repo}/${docker.name} + ${project.version} + + + + + + + + + push-docker-image + + + push-docker-image + + + + + + com.spotify + dockerfile-maven-plugin + + + push-latest-docker-image + pre-integration-test + + push + + + latest + ${docker.repo}/${docker.name} + + + + push-version-docker-image + pre-integration-test + + push + + + ${project.version} + ${docker.repo}/${docker.name} + + + + + + + + + + + jenkins + Jenkins Repository + https://repo.jenkins-ci.org/releases + + false + + + + diff --git a/pom.xml b/pom.xml index 4ec162be8f..063cb027cb 100755 --- a/pom.xml +++ b/pom.xml @@ -109,6 +109,7 @@ 1.0.2TB 3.4.0 7.54.2 + 2.8.5 @@ -838,6 +839,11 @@ coap ${project.version} + + org.thingsboard.common.transport + snmp + ${project.version} + org.thingsboard dao @@ -1408,6 +1414,11 @@ + + org.snmp4j + snmp4j + ${snmp4j.version} + diff --git a/transport/pom.xml b/transport/pom.xml index bcafb9a184..98429f2329 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -37,6 +37,7 @@ http mqtt coap + snmp diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml new file mode 100644 index 0000000000..fbcca16149 --- /dev/null +++ b/transport/snmp/pom.xml @@ -0,0 +1,112 @@ + + + 4.0.0 + + + org.thingsboard + 3.2.1-SNAPSHOT + transport + + + org.thingsboard.transport + snmp + jar + + Thingsboard SNMP Transport Service + https://thingsboard.io + + + UTF-8 + ${basedir}/../.. + java + false + process-resources + package + tb-snmp-transport + false + ${project.build.directory}/windows + ThingsBoard SNMP Transport Service + org.thingsboard.server.snmp.ThingsboardSnmpTransportApplication + + + + + org.thingsboard.common.transport + snmp + + + org.thingsboard.common + queue + + + org.springframework.boot + spring-boot-starter-web + + + + + ${pkg.name}-${project.version} + + + ${project.basedir}/src/main/resources + + + + + org.apache.maven.plugins + maven-resources-plugin + + + org.apache.maven.plugins + maven-dependency-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + org.springframework.boot + spring-boot-maven-plugin + + + org.thingsboard + gradle-maven-plugin + + + org.apache.maven.plugins + maven-assembly-plugin + + + org.apache.maven.plugins + maven-install-plugin + + + + + + jenkins + Jenkins Repository + https://repo.jenkins-ci.org/releases + + false + + + + diff --git a/transport/snmp/src/main/java/org/thingsboard/server/snmp/ThingsboardSnmpTransportApplication.java b/transport/snmp/src/main/java/org/thingsboard/server/snmp/ThingsboardSnmpTransportApplication.java new file mode 100644 index 0000000000..47d96db8c5 --- /dev/null +++ b/transport/snmp/src/main/java/org/thingsboard/server/snmp/ThingsboardSnmpTransportApplication.java @@ -0,0 +1,48 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.SpringBootConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.EnableScheduling; + +import java.util.Arrays; + +@SpringBootConfiguration +@EnableAsync +@EnableScheduling +@ComponentScan({"org.thingsboard.server.snmp", "org.thingsboard.server.common", "org.thingsboard.server.transport.snmp", "org.thingsboard.server.queue"}) +public class ThingsboardSnmpTransportApplication { + + private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; + private static final String DEFAULT_SPRING_CONFIG_PARAM = SPRING_CONFIG_NAME_KEY + "=" + "tb-snmp-transport"; + + public static void main(String[] args) { + SpringApplication.run(ThingsboardSnmpTransportApplication.class, updateArguments(args)); + } + + private static String[] updateArguments(String[] args) { + if (Arrays.stream(args).noneMatch(arg -> arg.startsWith(SPRING_CONFIG_NAME_KEY))) { + String[] modifiedArgs = new String[args.length + 1]; + System.arraycopy(args, 0, modifiedArgs, 0, args.length); + modifiedArgs[args.length] = DEFAULT_SPRING_CONFIG_PARAM; + return modifiedArgs; + } + return args; + } +} diff --git a/transport/snmp/src/main/resources/device-data-config.json b/transport/snmp/src/main/resources/device-data-config.json new file mode 100644 index 0000000000..e425ccc3a6 --- /dev/null +++ b/transport/snmp/src/main/resources/device-data-config.json @@ -0,0 +1,9 @@ +{ + "deviceName": "Thermostat T1", + "snmpConfig": { + "address": "192.168.1.2", + "port": 161, + "community": "U5J=$HWj6f@7", + "protocolVersion": "v2c" + } +} diff --git a/transport/snmp/src/main/resources/device-profile-config.json b/transport/snmp/src/main/resources/device-profile-config.json new file mode 100644 index 0000000000..b272913045 --- /dev/null +++ b/transport/snmp/src/main/resources/device-profile-config.json @@ -0,0 +1,21 @@ +{ + "poolPeriodMs": 10000, + "timeoutMs": 5000, + "retries": 5, + "attributes": [ + { + "key": "snmpNodeManagerEmail", + "type": "STRING", + "method": "get", + "oid": ".1.3.6.1.2.1.1.4.0" + } + ], + "telemetry": [ + { + "key": "snmpNodeSysUpTime", + "type": "LONG", + "method": "get", + "oid": ".1.3.6.1.2.1.1.3.0" + } + ] +} \ No newline at end of file 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 3552821a8b..abcbb5989f 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 @@ -97,6 +97,7 @@ import { DeviceProfileDialogComponent } from '@home/components/profile/device-pr 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 { SnmpDeviceProfileTransportConfigurationComponent } from './profile/device/snmp-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'; @@ -199,6 +200,7 @@ import { CopyDeviceCredentialsComponent } from '@home/components/device/copy-dev DefaultDeviceProfileTransportConfigurationComponent, MqttDeviceProfileTransportConfigurationComponent, Lwm2mDeviceProfileTransportConfigurationComponent, + SnmpDeviceProfileTransportConfigurationComponent, DeviceProfileTransportConfigurationComponent, CreateAlarmRulesComponent, AlarmRuleComponent, @@ -288,6 +290,7 @@ import { CopyDeviceCredentialsComponent } from '@home/components/device/copy-dev DefaultDeviceProfileTransportConfigurationComponent, MqttDeviceProfileTransportConfigurationComponent, Lwm2mDeviceProfileTransportConfigurationComponent, + SnmpDeviceProfileTransportConfigurationComponent, DeviceProfileTransportConfigurationComponent, CreateAlarmRulesComponent, AlarmRuleComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html index 78707a9221..b877ef0015 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.html @@ -35,5 +35,11 @@ formControlName="configuration"> + + + + diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.html new file mode 100644 index 0000000000..f57b1ac716 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.html @@ -0,0 +1,23 @@ + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts new file mode 100644 index 0000000000..96f7454cba --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2021 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, + SnmpDeviceProfileTransportConfiguration +} from '@shared/models/device.models'; +import {isDefinedAndNotNull} from "@core/utils"; + +export interface OidMappingConfiguration { + isAttribute: boolean; + key: string; + type: string; + method: string; + oid: string; +} + +@Component({ + selector: 'tb-snmp-device-profile-transport-configuration', + templateUrl: './snmp-device-profile-transport-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent), + multi: true + }] +}) +export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit { + snmpDeviceProfileTransportConfigurationFormGroup: FormGroup; + private requiredValue: boolean; + private configuration = []; + + 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) { + } + + ngOnInit(): void { + this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({ + configuration: [null, Validators.required] + }); + this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + writeValue(value: SnmpDeviceProfileTransportConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.snmpDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: DeviceProfileTransportConfiguration = null; + if (this.snmpDeviceProfileTransportConfigurationFormGroup.valid) { + configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; + configuration.type = DeviceTransportType.SNMP; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html index 73cceba5bc..02799a840c 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.html @@ -35,5 +35,11 @@ formControlName="configuration"> + + + + diff --git a/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.html new file mode 100644 index 0000000000..fc9f615db9 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.html @@ -0,0 +1,24 @@ + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.ts new file mode 100644 index 0000000000..165a2906f4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.ts @@ -0,0 +1,100 @@ +/// +/// Copyright © 2016-2021 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 { + DeviceTransportConfiguration, + DeviceTransportType, + SnmpDeviceTransportConfiguration +} from '@shared/models/device.models'; + +@Component({ + selector: 'tb-snmp-device-transport-configuration', + templateUrl: './snmp-device-transport-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SnmpDeviceTransportConfigurationComponent), + multi: true + }] +}) +export class SnmpDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { + + snmpDeviceTransportConfigurationFormGroup: 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.snmpDeviceTransportConfigurationFormGroup = this.fb.group({ + configuration: [null, Validators.required] + }); + this.snmpDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.snmpDeviceTransportConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.snmpDeviceTransportConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: SnmpDeviceTransportConfiguration | null): void { + this.snmpDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + } + + private updateModel() { + let configuration: DeviceTransportConfiguration = null; + if (this.snmpDeviceTransportConfigurationFormGroup.valid) { + configuration = this.snmpDeviceTransportConfigurationFormGroup.getRawValue().configuration; + configuration.type = DeviceTransportType.SNMP; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts index 7ba9a5c0fe..9347fab505 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -31,6 +31,7 @@ import { DefaultDeviceTransportConfigurationComponent } from './data/default-dev import { DeviceTransportConfigurationComponent } from './data/device-transport-configuration.component'; import { MqttDeviceTransportConfigurationComponent } from './data/mqtt-device-transport-configuration.component'; import { Lwm2mDeviceTransportConfigurationComponent } from './data/lwm2m-device-transport-configuration.component'; +import { SnmpDeviceTransportConfigurationComponent } from './data/snmp-device-transport-configuration.component'; @NgModule({ declarations: [ @@ -39,6 +40,7 @@ import { Lwm2mDeviceTransportConfigurationComponent } from './data/lwm2m-device- DefaultDeviceTransportConfigurationComponent, MqttDeviceTransportConfigurationComponent, Lwm2mDeviceTransportConfigurationComponent, + SnmpDeviceTransportConfigurationComponent, DeviceTransportConfigurationComponent, DeviceDataComponent, DeviceComponent, diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index dcf3f34093..fd293aae64 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -29,13 +29,15 @@ import * as _moment from 'moment'; import { AbstractControl, ValidationErrors } from '@angular/forms'; export enum DeviceProfileType { - DEFAULT = 'DEFAULT' + DEFAULT = 'DEFAULT', + SNMP = 'SNMP' } export enum DeviceTransportType { DEFAULT = 'DEFAULT', MQTT = 'MQTT', // LWM2M = 'LWM2M' + SNMP = 'SNMP' } export enum MqttTransportPayloadType { @@ -68,6 +70,13 @@ export const deviceProfileTypeConfigurationInfoMap = new Map( [DeviceTransportType.DEFAULT, 'device-profile.transport-type-default-hint'], [DeviceTransportType.MQTT, 'device-profile.transport-type-mqtt-hint'], // [DeviceTransportType.LWM2M, 'device-profile.transport-type-lwm2m-hint'] + [DeviceTransportType.SNMP, 'device-profile.transport-type-snmp-hint'], ] ); @@ -128,6 +139,13 @@ export const deviceTransportTypeConfigurationInfoMap = new Map[+] and multi-level [#] wildcards supported.", "telemetry-topic-filter": "Telemetry topic filter", From 37b88908fd12bb80f5c2a3756feefa009bd9e8f9 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 10 Mar 2021 15:25:35 +0200 Subject: [PATCH 002/127] Fix merge errors --- common/transport/snmp/pom.xml | 2 +- msa/transport/snmp/pom.xml | 2 +- transport/snmp/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index 1f334b9699..c7e22ecc80 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.2.1-SNAPSHOT + 3.3.0-SNAPSHOT transport diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index df5caa658c..62448d8763 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.2.1-SNAPSHOT + 3.3.0-SNAPSHOT org.thingsboard.msa.transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index fbcca16149..ed446a5612 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.2.1-SNAPSHOT + 3.3.0-SNAPSHOT transport From 8f2438d6ab57ca22b940a001e9a05e68225e6c69 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 22 Mar 2021 17:17:42 +0200 Subject: [PATCH 003/127] SNMP devices balancing (#4254) * Fix merge errors * Implement SNMP transports balancing * Refactor; implement transport device cache * Refactor * Finish up device lifecycle handling implementing; refactor * Refactor * Change base image to thingsboard/openjdk11 for msa snmp transport * Refactor * Change transport services names to upper-case --- .../actors/service/DefaultActorService.java | 4 +- .../DefaultTbApiUsageStateService.java | 2 +- .../apiusage/TbApiUsageStateService.java | 2 +- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultTbRuleEngineConsumerService.java | 2 +- .../service/queue/TbCoreConsumerService.java | 2 +- .../queue/TbRuleEngineConsumerService.java | 2 +- .../processing/AbstractConsumerService.java | 2 +- .../state/DefaultDeviceStateService.java | 2 +- .../service/state/DeviceStateService.java | 2 +- .../DefaultSubscriptionManagerService.java | 2 +- .../DefaultTbLocalSubscriptionService.java | 5 +- .../SubscriptionManagerService.java | 2 +- .../TbLocalSubscriptionService.java | 4 +- .../AbstractSubscriptionService.java | 19 +- .../telemetry/AlarmSubscriptionService.java | 3 +- .../DefaultAlarmSubscriptionService.java | 23 - .../TelemetrySubscriptionService.java | 3 +- .../transport/DefaultTransportApiService.java | 94 ++++- .../server/dao/device/DeviceService.java | 3 + .../common/data/TbTransportService.java | 20 + .../data/DeviceTransportConfiguration.java | 4 +- .../SnmpProfileTransportConfiguration.java | 10 +- .../DefaultTbServiceInfoProvider.java | 20 + .../queue/discovery/HashPartitionService.java | 20 +- .../queue/discovery/PartitionService.java | 4 + .../discovery/TbApplicationEventListener.java | 1 + .../queue/discovery/ZkDiscoveryService.java | 12 +- .../ClusterTopologyChangeEvent.java | 3 +- .../{ => event}/PartitionChangeEvent.java | 3 +- .../event/ServiceListChangedEvent.java | 35 ++ .../{ => event}/TbApplicationEvent.java | 2 +- .../queue/util/TbSnmpTransportComponent.java | 29 ++ common/queue/src/main/proto/queue.proto | 35 ++ .../transport/coap/CoapTransportService.java | 9 +- .../transport/http/DeviceApiController.java | 10 +- .../lwm2m/server/LwM2mTransportService.java | 3 +- .../server/LwM2mTransportServiceImpl.java | 6 + .../transport/mqtt/MqttTransportService.java | 8 +- common/transport/snmp/pom.xml | 4 - .../transport/snmp/SnmpDeviceSimulator.java | 81 ++++ .../transport/snmp/SnmpTransportContext.java | 311 +++++++++++--- .../transport/snmp/SnmpTransportService.java | 147 ------- .../ServiceListChangedEventListener.java | 35 ++ .../event/SnmpTransportListChangedEvent.java | 24 ++ ...SnmpTransportListChangedEventListener.java | 34 ++ .../service/ProtoTransportEntityService.java | 92 ++++ .../SnmpTransportBalancingService.java | 92 ++++ .../SnmpTransportService.java} | 149 +++++-- .../snmp/session/DeviceSessionContext.java | 166 ++++++++ .../snmp/session/DeviceSessionCtx.java | 206 --------- .../common/transport/DeviceUpdatedEvent.java | 28 ++ .../common/transport/SessionMsgListener.java | 4 + .../common/transport/TransportContext.java | 1 + .../common/transport/TransportService.java | 12 + .../DefaultTransportDeviceProfileCache.java | 4 +- .../service/DefaultTransportService.java | 77 +++- .../server/dao/device/DeviceDao.java | 4 + .../server/dao/device/DeviceServiceImpl.java | 7 + .../dao/sql/device/DeviceRepository.java | 6 + .../server/dao/sql/device/JpaDeviceDao.java | 6 + docker/tb-transports/snmp/conf/logback.xml | 50 +++ .../snmp/conf/tb-snmp-transport.conf | 23 + msa/transport/snmp/docker/Dockerfile | 2 +- transport/snmp/src/main/conf/logback.xml | 45 ++ .../snmp/src/main/conf/tb-snmp-transport.conf | 22 + transport/snmp/src/main/resources/logback.xml | 36 ++ .../src/main/resources/tb-snmp-transport.yml | 394 ++++++++++++++++++ 68 files changed, 1923 insertions(+), 553 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/TbTransportService.java rename common/queue/src/main/java/org/thingsboard/server/queue/discovery/{ => event}/ClusterTopologyChangeEvent.java (91%) rename common/queue/src/main/java/org/thingsboard/server/queue/discovery/{ => event}/PartitionChangeEvent.java (93%) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ServiceListChangedEvent.java rename common/queue/src/main/java/org/thingsboard/server/queue/discovery/{ => event}/TbApplicationEvent.java (95%) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/TbSnmpTransportComponent.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java delete mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportService.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/ServiceListChangedEventListener.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEvent.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEventListener.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportBalancingService.java rename common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/{session/SnmpSessionListener.java => service/SnmpTransportService.java} (60%) create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java delete mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionCtx.java create mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceUpdatedEvent.java create mode 100644 docker/tb-transports/snmp/conf/logback.xml create mode 100644 docker/tb-transports/snmp/conf/tb-snmp-transport.conf create mode 100644 transport/snmp/src/main/conf/logback.xml create mode 100644 transport/snmp/src/main/conf/tb-snmp-transport.conf create mode 100644 transport/snmp/src/main/resources/logback.xml create mode 100644 transport/snmp/src/main/resources/tb-snmp-transport.yml diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index 05363dfd59..f3a07eb727 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -25,7 +25,6 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.DefaultTbActorSystem; -import org.thingsboard.server.actors.TbActorId; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbActorSystem; import org.thingsboard.server.actors.TbActorSystemSettings; @@ -33,14 +32,13 @@ import org.thingsboard.server.actors.app.AppActor; import org.thingsboard.server.actors.app.AppInitMsg; import org.thingsboard.server.actors.stats.StatsActor; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; @Service @Slf4j 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 d0a3984660..ccac9b4a16 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 @@ -52,7 +52,7 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.scheduler.SchedulerComponent; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 117607ae9f..f97188597b 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; public interface TbApiUsageStateService extends ApplicationListener { 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 02f69ce10d..9d3e2b5a64 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 @@ -52,7 +52,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index 390798a3e2..bd29b6c431 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -38,7 +38,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbRuleEngineQueueFactory; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerService.java index 0a965413cf..222f58fd78 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbCoreConsumerService.java @@ -16,7 +16,7 @@ package org.thingsboard.server.service.queue; import org.springframework.context.ApplicationListener; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; public interface TbCoreConsumerService extends ApplicationListener { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerService.java index e4a88de575..582dbd1355 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleEngineConsumerService.java @@ -16,7 +16,7 @@ package org.thingsboard.server.service.queue; import org.springframework.context.ApplicationListener; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; public interface TbRuleEngineConsumerService extends ApplicationListener { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 02378eb557..28cce50bf5 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -34,7 +34,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index c8a4e80e0e..5c358c1b77 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -54,7 +54,7 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.util.TbCoreComponent; diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java index 932ed4720d..b1bbb9cdfa 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateService.java @@ -18,7 +18,7 @@ package org.thingsboard.server.service.state; import org.springframework.context.ApplicationListener; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.common.msg.queue.TbCallback; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java index ce8838aaa8..657bdf6f75 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultSubscriptionManagerService.java @@ -46,7 +46,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdate import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionUpdateValueListProto; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; 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 0220a94964..710d613b37 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 @@ -20,10 +20,9 @@ import org.springframework.beans.factory.annotation.Autowired; 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.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index fd40364fe5..37850b701f 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import java.util.List; diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java index d244ef136b..f1bf6c0da0 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbLocalSubscriptionService.java @@ -15,8 +15,8 @@ */ package org.thingsboard.server.service.subscription; -import org.thingsboard.server.queue.discovery.ClusterTopologyChangeEvent; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index 168e1271fd..23abba15f3 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -22,35 +22,18 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationListener; import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -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.kv.StringDataEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.queue.ServiceType; -import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; -import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbApplicationEventListener; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; -import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.util.Collections; -import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java index fa44906cc3..6ed3860341 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AlarmSubscriptionService.java @@ -17,8 +17,7 @@ package org.thingsboard.server.service.telemetry; import org.springframework.context.ApplicationListener; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; -import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; /** * Created by ashvayka on 27.03.18. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 107b97dbb3..dde71ef53f 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -22,9 +22,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmQuery; @@ -35,43 +33,22 @@ import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.AttributeKvEntry; -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.kv.StringDataEntry; -import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AlarmData; -import org.thingsboard.server.common.data.query.AlarmDataPageLink; import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; -import org.thingsboard.server.dao.attributes.AttributesService; -import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; -import org.thingsboard.server.service.telemetry.sub.AlarmSubscriptionUpdate; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; import java.util.Collection; -import java.util.Collections; -import java.util.List; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.function.Consumer; /** * Created by ashvayka on 27.03.18. diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java index 015a58495f..0ac9e79ff1 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/TelemetrySubscriptionService.java @@ -16,8 +16,7 @@ package org.thingsboard.server.service.telemetry; import org.springframework.context.ApplicationListener; -import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; /** * Created by ashvayka on 27.03.18. 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 eea7af582c..4f58bd0f63 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 @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.ApiUsageState; 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.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; @@ -61,11 +62,15 @@ import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetResourcesRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; @@ -84,6 +89,7 @@ import org.thingsboard.server.service.state.DeviceStateService; import java.util.Collections; import java.util.List; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -139,40 +145,41 @@ public class DefaultTransportApiService implements TransportApiService { @Override public ListenableFuture> handle(TbProtoQueueMsg tbProtoQueueMsg) { TransportApiRequestMsg transportApiRequestMsg = tbProtoQueueMsg.getValue(); + ListenableFuture result = null; + if (transportApiRequestMsg.hasValidateTokenRequestMsg()) { ValidateDeviceTokenRequestMsg msg = transportApiRequestMsg.getValidateTokenRequestMsg(); - return Futures.transform(validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = validateCredentials(msg.getToken(), DeviceCredentialsType.ACCESS_TOKEN); } else if (transportApiRequestMsg.hasValidateBasicMqttCredRequestMsg()) { TransportProtos.ValidateBasicMqttCredRequestMsg msg = transportApiRequestMsg.getValidateBasicMqttCredRequestMsg(); - return Futures.transform(validateCredentials(msg), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = validateCredentials(msg); } else if (transportApiRequestMsg.hasValidateX509CertRequestMsg()) { ValidateDeviceX509CertRequestMsg msg = transportApiRequestMsg.getValidateX509CertRequestMsg(); - return Futures.transform(validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = validateCredentials(msg.getHash(), DeviceCredentialsType.X509_CERTIFICATE); } else if (transportApiRequestMsg.hasGetOrCreateDeviceRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = handle(transportApiRequestMsg.getGetOrCreateDeviceRequestMsg()); } else if (transportApiRequestMsg.hasEntityProfileRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getEntityProfileRequestMsg()), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = handle(transportApiRequestMsg.getEntityProfileRequestMsg()); } else if (transportApiRequestMsg.hasLwM2MRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getLwM2MRequestMsg()), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = handle(transportApiRequestMsg.getLwM2MRequestMsg()); } else if (transportApiRequestMsg.hasValidateDeviceLwM2MCredentialsRequestMsg()) { ValidateDeviceLwM2MCredentialsRequestMsg msg = transportApiRequestMsg.getValidateDeviceLwM2MCredentialsRequestMsg(); - return Futures.transform(validateCredentials(msg.getCredentialsId(), DeviceCredentialsType.LWM2M_CREDENTIALS), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = validateCredentials(msg.getCredentialsId(), DeviceCredentialsType.LWM2M_CREDENTIALS); } else if (transportApiRequestMsg.hasProvisionDeviceRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = handle(transportApiRequestMsg.getProvisionDeviceRequestMsg()); } else if (transportApiRequestMsg.hasResourcesRequestMsg()) { - return Futures.transform(handle(transportApiRequestMsg.getResourcesRequestMsg()), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + result = handle(transportApiRequestMsg.getResourcesRequestMsg()); + } else if (transportApiRequestMsg.hasSnmpDevicesRequestMsg()) { + result = handle(transportApiRequestMsg.getSnmpDevicesRequestMsg()); + } else if (transportApiRequestMsg.hasDeviceRequestMsg()) { + result = handle(transportApiRequestMsg.getDeviceRequestMsg()); + } else if (transportApiRequestMsg.hasDeviceCredentialsRequestMsg()) { + result = handle(transportApiRequestMsg.getDeviceCredentialsRequestMsg()); } - return Futures.transform(getEmptyTransportApiResponseFuture(), - value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + + return Futures.transform(Optional.ofNullable(result).orElseGet(this::getEmptyTransportApiResponseFuture), + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), + MoreExecutors.directExecutor()); } private ListenableFuture validateCredentials(String credentialsId, DeviceCredentialsType credentialsType) { @@ -366,6 +373,39 @@ public class DefaultTransportApiService implements TransportApiService { return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build()); } + private ListenableFuture handle(GetDeviceRequestMsg requestMsg) { + DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB())); + Device device = deviceService.findDeviceById(TenantId.SYS_TENANT_ID, deviceId); + + TransportApiResponseMsg responseMsg; + if (device != null) { + UUID deviceProfileId = device.getDeviceProfileId().getId(); + responseMsg = TransportApiResponseMsg.newBuilder() + .setDeviceResponseMsg(TransportProtos.GetDeviceResponseMsg.newBuilder() + .setDeviceProfileIdMSB(deviceProfileId.getMostSignificantBits()) + .setDeviceProfileIdLSB(deviceProfileId.getLeastSignificantBits()) + .setDeviceTransportConfiguration(ByteString.copyFrom( + dataDecodingEncodingService.encode(device.getDeviceData().getTransportConfiguration()) + ))) + .build(); + } else { + responseMsg = TransportApiResponseMsg.getDefaultInstance(); + } + + return Futures.immediateFuture(responseMsg); + } + + private ListenableFuture handle(GetDeviceCredentialsRequestMsg requestMsg) { + DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB())); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(TenantId.SYS_TENANT_ID, deviceId); + + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder() + .setDeviceCredentialsResponseMsg(TransportProtos.GetDeviceCredentialsResponseMsg.newBuilder() + .setDeviceCredentialsData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceCredentials)))) + .build()); + } + + private ListenableFuture handle(GetResourcesRequestMsg requestMsg) { TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); TransportProtos.GetResourcesResponseMsg.Builder builder = TransportProtos.GetResourcesResponseMsg.newBuilder(); @@ -400,6 +440,20 @@ public class DefaultTransportApiService implements TransportApiService { .build(); } + // TODO: request snmp devices with pagination + private ListenableFuture handle(GetSnmpDevicesRequestMsg requestMsg) { + List result = deviceService.findDevicesIdsByDeviceProfileTransportType(DeviceTransportType.SNMP); + GetSnmpDevicesResponseMsg responseMsg = GetSnmpDevicesResponseMsg.newBuilder() + .addAllIds(result.stream() + .map(UUID::toString) + .collect(Collectors.toList())) + .build(); + + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder() + .setSnmpDevicesResponseMsg(responseMsg) + .build()); + } + private ListenableFuture getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> { if (device == null) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index f98c56d89a..1a893f5388 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.id.CustomerId; @@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import java.util.List; +import java.util.UUID; public interface DeviceService { @@ -90,4 +92,5 @@ public interface DeviceService { Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile); + List findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbTransportService.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbTransportService.java new file mode 100644 index 0000000000..6195022aba --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbTransportService.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2021 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; + +public interface TbTransportService { + String getName(); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java index 0ae9f26cf3..49a547371f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java @@ -21,6 +21,8 @@ import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.DeviceTransportType; +import java.io.Serializable; + @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, @@ -31,7 +33,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = MqttDeviceTransportConfiguration.class, name = "MQTT"), @JsonSubTypes.Type(value = Lwm2mDeviceTransportConfiguration.class, name = "LWM2M"), @JsonSubTypes.Type(value = SnmpDeviceTransportConfiguration.class, name = "SNMP")}) -public interface DeviceTransportConfiguration { +public interface DeviceTransportConfiguration extends Serializable { @JsonIgnore DeviceTransportType getType(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java index a2529e74e9..50f6c4351c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java @@ -19,14 +19,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.thingsboard.server.common.data.DeviceTransportType; +import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; @Data public class SnmpProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - - private int poolPeriodMs; + private int pollPeriodMs; private int timeoutMs; private int retries; private List attributes; @@ -39,6 +39,10 @@ public class SnmpProfileTransportConfiguration implements DeviceProfileTransport @JsonIgnore public List getKvMappings() { - return Stream.concat(attributes.stream(), telemetry.stream()).collect(Collectors.toList()); + if (attributes != null && telemetry != null) { + return Stream.concat(attributes.stream(), telemetry.stream()).collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java index fc4ec33eb1..3ba360cd8e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java @@ -19,8 +19,12 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationContext; +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.transport.TransportProtos; @@ -32,6 +36,7 @@ import javax.annotation.PostConstruct; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -56,6 +61,8 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider { @Autowired(required = false) private TbQueueRuleEngineSettings ruleEngineSettings; + @Autowired + private ApplicationContext applicationContext; private List serviceTypes; private ServiceInfo serviceInfo; @@ -102,6 +109,19 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider { serviceInfo = builder.build(); } + @EventListener(ContextRefreshedEvent.class) + public void setTransports() { + serviceInfo = ServiceInfo.newBuilder(serviceInfo) + .addAllTransports(getTransportServices().stream() + .map(TbTransportService::getName) + .collect(Collectors.toSet())) + .build(); + } + + private Collection getTransportServices() { + return applicationContext.getBeansOfType(TbTransportService.class).values(); + } + @Override public ServiceInfo getServiceInfo() { return serviceInfo; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java index 2da438417a..c833b314e6 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/HashPartitionService.java @@ -15,26 +15,27 @@ */ package org.thingsboard.server.queue.discovery; -import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; +import com.google.common.hash.Hasher; import com.google.common.hash.Hashing; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.msg.queue.ServiceQueueKey; import org.thingsboard.server.common.msg.queue.ServiceQueue; +import org.thingsboard.server.common.msg.queue.ServiceQueueKey; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; +import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import javax.annotation.PostConstruct; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -46,7 +47,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ConcurrentNavigableMap; import java.util.stream.Collectors; @Service @@ -186,6 +186,8 @@ public class HashPartitionService implements PartitionService { applicationEventPublisher.publishEvent(new ClusterTopologyChangeEvent(this, changes)); } } + + applicationEventPublisher.publishEvent(new ServiceListChangedEvent(otherServices, currentService)); } @Override @@ -219,6 +221,14 @@ public class HashPartitionService implements PartitionService { } } + @Override + public int resolvePartitionIndex(UUID entityId, int partitions) { + int hash = hashFunction.newHasher() + .putLong(entityId.getMostSignificantBits()) + .putLong(entityId.getLeastSignificantBits()).hash().asInt(); + return Math.abs(hash % partitions); + } + private Map> getServiceKeyListMap(List services) { final Map> currentMap = new HashMap<>(); services.forEach(serviceInfo -> { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java index ebb260bc57..20c59378e8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionService.java @@ -20,9 +20,11 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; import java.util.List; import java.util.Set; +import java.util.UUID; /** * Once application is ready or cluster topology changes, this Service will produce {@link PartitionChangeEvent} @@ -55,4 +57,6 @@ public interface PartitionService { * @return */ TopicPartitionInfo getNotificationsTopic(ServiceType serviceType, String serviceId); + + int resolvePartitionIndex(UUID entityId, int partitions); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java index 9158d8f0c8..70484a892b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEventListener.java @@ -17,6 +17,7 @@ package org.thingsboard.server.queue.discovery; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationListener; +import org.thingsboard.server.queue.discovery.event.TbApplicationEvent; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 5253f57248..0802f0f52c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -33,12 +33,14 @@ import org.apache.zookeeper.KeeperException; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -77,7 +79,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi private volatile boolean stopped = true; - public ZkDiscoveryService(TbServiceInfoProvider serviceInfoProvider, PartitionService partitionService) { + public ZkDiscoveryService(TbServiceInfoProvider serviceInfoProvider, + PartitionService partitionService) { this.serviceInfoProvider = serviceInfoProvider; this.partitionService = partitionService; } @@ -126,7 +129,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi return; } publishCurrentServer(); - partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); + TransportProtos.ServiceInfo currentService = serviceInfoProvider.getServiceInfo(); + partitionService.recalculatePartitions(currentService, getOtherServers()); } public synchronized void publishCurrentServer() { @@ -281,11 +285,11 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi case CHILD_ADDED: case CHILD_UPDATED: case CHILD_REMOVED: - partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); + TransportProtos.ServiceInfo currentService = serviceInfoProvider.getServiceInfo(); + partitionService.recalculatePartitions(currentService, getOtherServers()); break; default: break; } } - } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ClusterTopologyChangeEvent.java similarity index 91% rename from common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java rename to common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ClusterTopologyChangeEvent.java index 1e5b90b5fe..6b3a3ff93f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ClusterTopologyChangeEvent.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ClusterTopologyChangeEvent.java @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.queue.discovery; +package org.thingsboard.server.queue.discovery.event; import lombok.Getter; -import org.springframework.context.ApplicationEvent; import org.thingsboard.server.common.msg.queue.ServiceQueueKey; import java.util.Set; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/PartitionChangeEvent.java similarity index 93% rename from common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java rename to common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/PartitionChangeEvent.java index 2edcd2ceca..e11e19db61 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/PartitionChangeEvent.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/PartitionChangeEvent.java @@ -13,10 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.queue.discovery; +package org.thingsboard.server.queue.discovery.event; import lombok.Getter; -import org.springframework.context.ApplicationEvent; import org.thingsboard.server.common.msg.queue.ServiceQueueKey; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ServiceListChangedEvent.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ServiceListChangedEvent.java new file mode 100644 index 0000000000..dd7d384640 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/ServiceListChangedEvent.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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.queue.discovery.event; + +import lombok.Getter; +import lombok.ToString; +import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; + +import java.util.List; + +@Getter +@ToString +public class ServiceListChangedEvent extends TbApplicationEvent { + private final List otherServices; + private final ServiceInfo currentService; + + public ServiceListChangedEvent(List otherServices, ServiceInfo currentService) { + super(otherServices); + this.otherServices = otherServices; + this.currentService = currentService; + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEvent.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/TbApplicationEvent.java similarity index 95% rename from common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEvent.java rename to common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/TbApplicationEvent.java index face2d36d6..1a75b4e4e4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/TbApplicationEvent.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/event/TbApplicationEvent.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.queue.discovery; +package org.thingsboard.server.queue.discovery.event; import lombok.Getter; import org.springframework.context.ApplicationEvent; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/TbSnmpTransportComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbSnmpTransportComponent.java new file mode 100644 index 0000000000..36080afaab --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/TbSnmpTransportComponent.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2021 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.queue.util; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')") +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE, ElementType.METHOD}) +public @interface TbSnmpTransportComponent { +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 42f6267d42..59fcdad36a 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -34,6 +34,7 @@ message ServiceInfo { int64 tenantIdMSB = 3; int64 tenantIdLSB = 4; repeated QueueInfo ruleEngineQueues = 5; + repeated string transports = 6; } /** @@ -250,6 +251,34 @@ message GetEntityProfileResponseMsg { bytes apiState = 3; } +message GetDeviceRequestMsg { + int64 deviceIdMSB = 1; + int64 deviceIdLSB = 2; +} + +message GetDeviceResponseMsg { + int64 deviceProfileIdMSB = 1; + int64 deviceProfileIdLSB = 2; + bytes deviceTransportConfiguration = 3; +} + +message GetDeviceCredentialsRequestMsg { + int64 deviceIdMSB = 1; + int64 deviceIdLSB = 2; +} + +message GetDeviceCredentialsResponseMsg { + bytes deviceCredentialsData = 1; +} + +message GetSnmpDevicesRequestMsg { + +} + +message GetSnmpDevicesResponseMsg { + repeated string ids = 1; +} + message EntityUpdateMsg { string entityType = 1; bytes data = 2; @@ -559,6 +588,9 @@ message TransportApiRequestMsg { ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; GetResourcesRequestMsg resourcesRequestMsg = 9; + GetSnmpDevicesRequestMsg snmpDevicesRequestMsg = 10; + GetDeviceRequestMsg deviceRequestMsg = 11; + GetDeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 12; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -567,8 +599,11 @@ message TransportApiResponseMsg { GetOrCreateDeviceFromGatewayResponseMsg getOrCreateDeviceResponseMsg = 2; GetEntityProfileResponseMsg entityProfileResponseMsg = 3; ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; + GetSnmpDevicesResponseMsg snmpDevicesResponseMsg = 5; LwM2MResponseMsg lwM2MResponseMsg = 6; GetResourcesResponseMsg resourcesResponseMsg = 7; + GetDeviceResponseMsg deviceResponseMsg = 8; + GetDeviceCredentialsResponseMsg deviceCredentialsResponseMsg = 9; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index 8abc859b21..dd4a76dcc8 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -18,12 +18,12 @@ package org.thingsboard.server.transport.coap; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.CoapServer; - import org.eclipse.californium.core.network.CoapEndpoint; import org.eclipse.californium.core.network.CoapEndpoint.Builder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.TbTransportService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -34,7 +34,7 @@ import java.net.UnknownHostException; @Service("CoapTransportService") @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") @Slf4j -public class CoapTransportService { +public class CoapTransportService implements TbTransportService { private static final String V1 = "v1"; private static final String API = "api"; @@ -73,4 +73,9 @@ public class CoapTransportService { this.server.destroy(); log.info("CoAP transport stopped!"); } + + @Override + public String getName() { + return "COAP"; + } } diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index e222b690ef..011093149e 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -31,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportContext; @@ -41,7 +42,6 @@ import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; @@ -53,7 +53,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import javax.servlet.http.HttpServletRequest; @@ -69,7 +68,7 @@ import java.util.function.Consumer; @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.http.enabled}'=='true')") @RequestMapping("/api/v1") @Slf4j -public class DeviceApiController { +public class DeviceApiController implements TbTransportService { @Autowired private HttpTransportContext transportContext; @@ -338,4 +337,9 @@ public class DeviceApiController { .build(), TransportServiceCallback.EMPTY); } + @Override + public String getName() { + return "HTTP"; + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java index 8d1aff37b5..db419eca9f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java @@ -20,12 +20,13 @@ import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Collection; import java.util.Optional; -public interface LwM2mTransportService { +public interface LwM2mTransportService extends TbTransportService { void onRegistered(Registration registration, Collection previousObsersations); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java index 548d750a3f..bae6a0af1d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java @@ -40,6 +40,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; 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.transport.TransportService; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -1118,4 +1119,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values())); return new ArrayList<>(namesIsWritable); } + + @Override + public String getName() { + return "LWM2M"; + } } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java index 0a0c0cbe70..3e53dec227 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportService.java @@ -28,6 +28,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.TbTransportService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -38,7 +39,7 @@ import javax.annotation.PreDestroy; @Service("MqttTransportService") @ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.mqtt.enabled}'=='true')") @Slf4j -public class MqttTransportService { +public class MqttTransportService implements TbTransportService { @Value("${transport.mqtt.bind_address}") private String host; @@ -90,4 +91,9 @@ public class MqttTransportService { } log.info("MQTT transport stopped!"); } + + @Override + public String getName() { + return "MQTT"; + } } diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index c7e22ecc80..ab83ab69a7 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -58,9 +58,5 @@ org.snmp4j snmp4j - - org.thingsboard.common - dao-api - diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java new file mode 100644 index 0000000000..7109ef4e3f --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java @@ -0,0 +1,81 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import org.snmp4j.CommunityTarget; +import org.snmp4j.PDU; +import org.snmp4j.Snmp; +import org.snmp4j.Target; +import org.snmp4j.event.ResponseEvent; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.VariableBinding; +import org.snmp4j.transport.DefaultUdpTransportMapping; +import org.snmp4j.transport.UdpTransportMapping; + +import java.io.IOException; + +/** + * For testing purposes. Will be removed when the time comes + */ +public class SnmpDeviceSimulator { + private final Target target; + private final OID oid = new OID(".1.3.6.1.2.1.1.1.0"); + private Snmp snmp; + + public SnmpDeviceSimulator(int port) { + String address = "udp:127.0.0.1/" + port; + + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString("public")); + target.setAddress(GenericAddress.parse(address)); + target.setRetries(2); + target.setTimeout(1500); + target.setVersion(SnmpConstants.version2c); + + this.target = target; + } + + public static void main(String[] args) throws IOException { + SnmpDeviceSimulator deviceSimulator = new SnmpDeviceSimulator(161); + + deviceSimulator.start(); + String response = deviceSimulator.sendRequest(PDU.GET); + + System.out.println(response); + } + + public void start() throws IOException { + UdpTransportMapping transport = new DefaultUdpTransportMapping(); + transport.addTransportListener((sourceTransport, incomingAddress, wholeMessage, tmStateReference) -> { + System.out.println(); + }); + snmp = new Snmp(transport); + + transport.listen(); + } + + public String sendRequest(int pduType) throws IOException { + PDU pdu = new PDU(); + pdu.add(new VariableBinding(oid)); + pdu.setType(pduType); + + ResponseEvent responseEvent = snmp.send(pdu, target); + return responseEvent.getResponse().get(0).getVariable().toString(); + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 66f852a209..a9fa8380fa 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -15,17 +15,19 @@ */ package org.thingsboard.server.transport.snmp; -import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; -import org.snmp4j.Snmp; import org.snmp4j.smi.OID; import org.snmp4j.smi.VariableBinding; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Service; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; 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.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileKvMapping; import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; @@ -33,84 +35,180 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.TransportContext; -import org.thingsboard.server.dao.device.DeviceCredentialsService; -import org.thingsboard.server.transport.snmp.session.DeviceSessionCtx; +import org.thingsboard.server.common.transport.TransportDeviceProfileCache; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.SessionInfoCreator; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.service.ProtoTransportEntityService; +import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingService; +import org.thingsboard.server.transport.snmp.service.SnmpTransportService; +import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; -@Service("SnmpTransportContext") -@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')") +@TbSnmpTransportComponent +@Component @Slf4j +@RequiredArgsConstructor public class SnmpTransportContext extends TransportContext { - @Autowired - DeviceCredentialsService deviceCredentialsService; - - @Autowired - SnmpTransportService snmpTransportService; - - @Getter - private final Map profileTransportConfig = new ConcurrentHashMap<>(); - @Getter - private final Map> pdusPerProfile = new ConcurrentHashMap<>(); - @Getter - private final Map deviceSessions = new ConcurrentHashMap<>(); - - public Optional findAttributesMapping(DeviceProfileId deviceProfileId, OID responseOid) { - if (profileTransportConfig.containsKey(deviceProfileId)) { - return findMapping(responseOid, profileTransportConfig.get(deviceProfileId).getAttributes()); - } - return Optional.empty(); + private final SnmpTransportService snmpTransportService; + private final TransportDeviceProfileCache deviceProfileCache; + private final TransportService transportService; + private final ProtoTransportEntityService protoEntityService; + private final SnmpTransportBalancingService balancingService; + + private final Map sessions = new ConcurrentHashMap<>(); + private final Map profilesTransportConfigs = new ConcurrentHashMap<>(); + private final Map> profilesPdus = new ConcurrentHashMap<>(); + private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); + + @EventListener(ApplicationReadyEvent.class) + @Order(2) + public void initDevicesSessions() { + log.info("Initializing SNMP devices sessions"); + allSnmpDevicesIds = protoEntityService.getAllSnmpDevicesIds().stream() + .map(DeviceId::new) + .collect(Collectors.toList()); + log.trace("Found all SNMP devices ids: {}", allSnmpDevicesIds); + + List managedDevicesIds = allSnmpDevicesIds.stream() + .filter(deviceId -> balancingService.isManagedByCurrentTransport(deviceId.getId())) + .collect(Collectors.toList()); + log.info("SNMP devices managed by current SNMP transport: {}", managedDevicesIds); + + managedDevicesIds.stream() + .map(protoEntityService::getDeviceById) + .collect(Collectors.toList()) + .forEach(this::establishDeviceSession); } - public Optional findTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { - if (profileTransportConfig.containsKey(deviceProfileId)) { - return findMapping(responseOid, profileTransportConfig.get(deviceProfileId).getTelemetry()); + private void establishDeviceSession(Device device) { + if (device == null) return; + log.info("Establishing SNMP device session for device {}", device.getId()); + + DeviceProfileId deviceProfileId = device.getDeviceProfileId(); + DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); + + DeviceCredentials credentials = protoEntityService.getDeviceCredentialsByDeviceId(device.getId()); + if (credentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { + log.warn("[{}] Expected credentials type is {} but found {}", device.getId(), DeviceCredentialsType.ACCESS_TOKEN, credentials.getCredentialsType()); + return; } - return Optional.empty(); - } - private Optional findMapping(OID responseOid, List mappings) { - return mappings.stream() - .filter(kvMapping -> new OID(kvMapping.getOid()).equals(responseOid)) - //TODO: OID shouldn't be duplicated in the config, add backend and UI verification - .findFirst(); - } + SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); + if (!deviceTransportConfiguration.isValid()) { + log.warn("SNMP device transport configuration is not valid"); + return; + } - public void initPduListPerProfile() { - profileTransportConfig.forEach(this::updatePduListPerProfile); - } + SnmpProfileTransportConfiguration profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + profilesPdus.computeIfAbsent(deviceProfileId, id -> createPdus(profileTransportConfiguration)); - public void updatePduListPerProfile(DeviceProfileId id, SnmpProfileTransportConfiguration config) { - pdusPerProfile.put(id, createPduList(config)); + DeviceSessionContext deviceSessionContext = new DeviceSessionContext( + device, deviceProfile, + credentials.getCredentialsId(), deviceTransportConfiguration, + this, snmpTransportService + ); + registerSessionMsgListener(deviceSessionContext); + sessions.put(device.getId(), deviceSessionContext); + log.info("Established SNMP device session for device {}", device.getId()); } - public void updateDeviceSessionCtx(Device device, DeviceProfile deviceProfile, Snmp snmp) { - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getTenantId(), device.getId()); - if (DeviceCredentialsType.ACCESS_TOKEN.equals(credentials.getCredentialsType())) { - SnmpDeviceTransportConfiguration snmpDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - if (snmpDeviceTransportConfiguration.isValid()) { - DeviceSessionCtx deviceSessionCtx = new DeviceSessionCtx(this, credentials.getCredentialsId(), snmpDeviceTransportConfiguration, snmp, device.getId(), deviceProfile); - deviceSessionCtx.createSessionInfo(ctx -> getTransportService().registerAsyncSession(deviceSessionCtx.getSessionInfo(), deviceSessionCtx)); - this.deviceSessions.put(device.getId(), deviceSessionCtx); - } - } else { + private void updateDeviceSession(DeviceSessionContext sessionContext, Device device, DeviceProfile deviceProfile) { + log.info("Updating SNMP device session for device {}", device.getId()); + DeviceProfileId deviceProfileId = deviceProfile.getId(); + + DeviceCredentials credentials = protoEntityService.getDeviceCredentialsByDeviceId(device.getId()); + if (credentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { log.warn("[{}] Expected credentials type is {} but found {}", device.getId(), DeviceCredentialsType.ACCESS_TOKEN, credentials.getCredentialsType()); + destroyDeviceSession(sessionContext); + return; + } + + SnmpProfileTransportConfiguration profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); + sessionContext.setProfileTransportConfiguration(profileTransportConfiguration); + sessionContext.setDeviceTransportConfiguration(deviceTransportConfiguration); + if (!deviceTransportConfiguration.isValid()) { + log.warn("SNMP device transport configuration is not valid"); + destroyDeviceSession(sessionContext); + return; + } + + if (!profileTransportConfiguration.equals(profilesTransportConfigs.get(deviceProfileId))) { + profilesPdus.put(deviceProfileId, createPdus(profileTransportConfiguration)); + profilesTransportConfigs.put(deviceProfileId, profileTransportConfiguration); + sessionContext.initTarget(profileTransportConfiguration, deviceTransportConfiguration); + } else if (!deviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { + sessionContext.initTarget(profileTransportConfiguration, deviceTransportConfiguration); + } else { + log.trace("Configuration of the device {} was not updated", device); } } - public ExecutorService getSnmpCallbackExecutor() { - return snmpTransportService.getSnmpCallbackExecutor(); + private void destroyDeviceSession(DeviceSessionContext sessionContext) { + if (sessionContext == null) return; + log.info("Destroying SNMP device session for device {}", sessionContext.getDevice().getId()); + sessionContext.close(); + transportService.deregisterSession(sessionContext.getSessionInfo()); + sessions.remove(sessionContext.getDeviceId()); + log.trace("Deregistered and removed session"); + + DeviceProfileId deviceProfileId = sessionContext.getDeviceProfile().getId(); + if (sessions.values().stream() + .map(DeviceAwareSessionContext::getDeviceProfile) + .noneMatch(deviceProfile -> deviceProfile.getId().equals(deviceProfileId))) { + log.trace("Removed values for device profile {} from configs and pdus caches", deviceProfileId); + profilesTransportConfigs.remove(deviceProfileId); + profilesPdus.remove(deviceProfileId); + } } - private List createPduList(SnmpProfileTransportConfiguration deviceProfileConfig) { + private void registerSessionMsgListener(DeviceSessionContext deviceSessionContext) { + transportService.process(DeviceTransportType.SNMP, + TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceSessionContext.getToken()).build(), + new TransportServiceCallback() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (msg.hasDeviceInfo()) { + SessionInfoProto sessionInfo = SessionInfoCreator.create( + msg, SnmpTransportContext.this, UUID.randomUUID() + ); + + transportService.registerAsyncSession(sessionInfo, deviceSessionContext); + deviceSessionContext.setSessionInfo(sessionInfo); + deviceSessionContext.setDeviceInfo(msg.getDeviceInfo()); + } else { + log.warn("[{}] Failed to process device auth", deviceSessionContext.getDeviceId()); + } + } + + @Override + public void onError(Throwable e) { + log.warn("[{}] Failed to process device auth: {}", deviceSessionContext.getDeviceId(), e); + } + }); + } + + private List createPdus(SnmpProfileTransportConfiguration deviceProfileConfig) { Map> varBindingPerMethod = new HashMap<>(); deviceProfileConfig.getKvMappings().forEach(mapping -> varBindingPerMethod @@ -127,7 +225,106 @@ public class SnmpTransportContext extends TransportContext { .collect(Collectors.toList()); } - //TODO: Extract SNMP methods to enum + @EventListener(DeviceUpdatedEvent.class) + public void onDeviceUpdatedOrCreated(DeviceUpdatedEvent deviceUpdatedEvent) { + Device device = deviceUpdatedEvent.getDevice(); + log.trace("Got creating or updating device event for device {}", device); + DeviceTransportType transportType = Optional.ofNullable(device.getDeviceData().getTransportConfiguration()) + .map(DeviceTransportConfiguration::getType) + .orElse(null); + if (!allSnmpDevicesIds.contains(device.getId())) { + if (transportType != DeviceTransportType.SNMP) { + return; + } + allSnmpDevicesIds.add(device.getId()); + if (balancingService.isManagedByCurrentTransport(device.getId().getId())) { + establishDeviceSession(device); + } + } else { + if (balancingService.isManagedByCurrentTransport(device.getId().getId())) { + DeviceSessionContext sessionContext = sessions.get(device.getId()); + if (transportType == DeviceTransportType.SNMP) { + if (sessionContext != null) { + updateDeviceSession(sessionContext, device, deviceProfileCache.get(device.getDeviceProfileId())); + } else { + establishDeviceSession(device); + } + } else { + log.trace("Transport type was changed to {}", transportType); + destroyDeviceSession(sessionContext); + } + } + } + } + + public void onDeviceDeleted(DeviceSessionContext sessionContext) { + destroyDeviceSession(sessionContext); + } + + public void onDeviceProfileUpdated(DeviceProfile deviceProfile, DeviceSessionContext sessionContext) { + updateDeviceSession(sessionContext, sessionContext.getDevice(), deviceProfile); + } + + public void onSnmpTransportListChanged() { + log.trace("SNMP transport list changed. Updating sessions"); + List deleted = new LinkedList<>(); + for (DeviceId deviceId : allSnmpDevicesIds) { + if (balancingService.isManagedByCurrentTransport(deviceId.getId())) { + if (!sessions.containsKey(deviceId)) { + Device device = protoEntityService.getDeviceById(deviceId); + if (device != null) { + log.info("SNMP device {} is now managed by current transport node", deviceId); + establishDeviceSession(device); + } else { + deleted.add(deviceId); + } + } + } else { + Optional.ofNullable(sessions.get(deviceId)) + .ifPresent(sessionContext -> { + log.info("SNMP session for device {} is not managed by current transport node anymore", deviceId); + destroyDeviceSession(sessionContext); + }); + } + } + log.trace("Removing deleted SNMP devices: {}", deleted); + allSnmpDevicesIds.removeAll(deleted); + } + + + public Collection getSessions() { + return sessions.values(); + } + + public Map> getProfilesPdus() { + return profilesPdus; + } + + public Optional getAttributesMapping(DeviceProfileId deviceProfileId, OID responseOid) { + if (profilesTransportConfigs.containsKey(deviceProfileId)) { + return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getAttributes()); + } + return Optional.empty(); + } + + public Optional getTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { + if (profilesTransportConfigs.containsKey(deviceProfileId)) { + return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getTelemetry()); + } + return Optional.empty(); + } + + private Optional getMapping(OID responseOid, List mappings) { + return mappings.stream() + .filter(kvMapping -> new OID(kvMapping.getOid()).equals(responseOid)) + //TODO: OID shouldn't be duplicated in the config, add backend and UI verification + .findFirst(); + } + + public ExecutorService getSnmpCallbackExecutor() { + return snmpTransportService.getSnmpCallbackExecutor(); + } + private int getSnmpMethod(String configMethod) { switch (configMethod) { case "get": diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportService.java deleted file mode 100644 index 5d8e4d84b4..0000000000 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportService.java +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright © 2016-2021 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.snmp; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.snmp4j.Snmp; -import org.snmp4j.transport.DefaultUdpTransportMapping; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.boot.context.event.ApplicationReadyEvent; -import org.springframework.context.event.EventListener; -import org.springframework.core.annotation.Order; -import org.springframework.stereotype.Service; -import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.DeviceInfo; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageDataIterable; -import org.thingsboard.server.dao.device.DeviceProfileService; -import org.thingsboard.server.dao.device.DeviceService; -import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.transport.snmp.session.DeviceSessionCtx; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -@Service("SnmpTransportService") -@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.snmp.enabled}'=='true')") -@Slf4j -public class SnmpTransportService { - - private static final int ENTITY_PACK_LIMIT = 1024; - - @Autowired - private SnmpTransportContext snmpTransportContext; - - @Autowired - DeviceProfileService deviceProfileService; - - @Autowired - TenantService tenantService; - - @Autowired - DeviceService deviceService; - - @Getter - private ExecutorService snmpCallbackExecutor; - private Snmp snmp; - private ScheduledExecutorService pollingExecutor; - - @PostConstruct - public void init() { - log.info("Starting SNMP transport..."); - pollingExecutor = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("snmp-polling")); - //TODO: Set parallelism value in the config - snmpCallbackExecutor = Executors.newWorkStealingPool(20); - initializeSnmp(); - log.info("SNMP transport started!"); - } - - @PreDestroy - public void shutdown() { - log.info("Stopping SNMP transport!"); - if (pollingExecutor != null) { - pollingExecutor.shutdownNow(); - } - if (snmpCallbackExecutor != null) { - snmpCallbackExecutor.shutdownNow(); - } - if (snmp != null) { - try { - snmp.close(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } - log.info("SNMP transport stopped!"); - } - - @EventListener(ApplicationReadyEvent.class) - @Order(value = 2) - public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { - log.info("Received application ready event. Starting SNMP polling."); - initSessionCtxList(); - startPolling(); - } - - private void initializeSnmp() { - try { - this.snmp = new Snmp(new DefaultUdpTransportMapping()); - this.snmp.listen(); - } catch (IOException e) { - //TODO: what should be done if transport wasn't initialized? - log.error(e.getMessage(), e); - } - } - - private void initSessionCtxList() { - //TODO: This approach works for monolith, in cluster the same data will be fetched by each node. - for (Tenant tenant : new PageDataIterable<>(tenantService::findTenants, ENTITY_PACK_LIMIT)) { - TenantId tenantId = tenant.getTenantId(); - for (DeviceProfile deviceProfile : new PageDataIterable<>(pageLink -> deviceProfileService.findDeviceProfiles(tenantId, pageLink), ENTITY_PACK_LIMIT)) { - if (DeviceTransportType.SNMP.equals(deviceProfile.getTransportType())) { - snmpTransportContext.getProfileTransportConfig().put(deviceProfile.getId(), - (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()); - initDeviceSessions(deviceProfile); - } - } - } - snmpTransportContext.initPduListPerProfile(); - } - - private void initDeviceSessions(DeviceProfile deviceProfile) { - for (DeviceInfo deviceInfo : new PageDataIterable<>(pageLink -> deviceService.findDeviceInfosByTenantIdAndDeviceProfileId(deviceProfile.getTenantId(), deviceProfile.getId(), pageLink), ENTITY_PACK_LIMIT)) { - snmpTransportContext.updateDeviceSessionCtx(deviceInfo, deviceProfile, snmp); - } - } - - private void startPolling() { - //TODO: Get poll period from configuration; - int poolPeriodSeconds = 1; - pollingExecutor.scheduleAtFixedRate(() -> snmpTransportContext.getDeviceSessions().values().forEach(DeviceSessionCtx::executeSnmpRequest), - 0, poolPeriodSeconds, TimeUnit.SECONDS); - } -} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/ServiceListChangedEventListener.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/ServiceListChangedEventListener.java new file mode 100644 index 0000000000..90b82abf96 --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/ServiceListChangedEventListener.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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.snmp.event; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingService; + +@TbSnmpTransportComponent +@Component +@RequiredArgsConstructor +public class ServiceListChangedEventListener extends TbApplicationEventListener { + private final SnmpTransportBalancingService snmpTransportBalancingService; + + @Override + protected void onTbApplicationEvent(ServiceListChangedEvent event) { + snmpTransportBalancingService.onServiceListChanged(event); + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEvent.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEvent.java new file mode 100644 index 0000000000..f7a4259b91 --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEvent.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2021 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.snmp.event; + +import org.thingsboard.server.queue.discovery.event.TbApplicationEvent; + +public class SnmpTransportListChangedEvent extends TbApplicationEvent { + public SnmpTransportListChangedEvent() { + super(new Object()); + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEventListener.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEventListener.java new file mode 100644 index 0000000000..1387461037 --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/event/SnmpTransportListChangedEventListener.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2021 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.snmp.event; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.discovery.TbApplicationEventListener; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.SnmpTransportContext; + +@TbSnmpTransportComponent +@Component +@RequiredArgsConstructor +public class SnmpTransportListChangedEventListener extends TbApplicationEventListener { + private final SnmpTransportContext snmpTransportContext; + + @Override + protected void onTbApplicationEvent(SnmpTransportListChangedEvent event) { + snmpTransportContext.onSnmpTransportListChanged(); + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java new file mode 100644 index 0000000000..1f639daa2a --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2021 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.snmp.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.device.data.DeviceData; +import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; + +@TbSnmpTransportComponent +@Service +@RequiredArgsConstructor +public class ProtoTransportEntityService { + private final TransportService transportService; + private final DataDecodingEncodingService dataDecodingEncodingService; + + public Device getDeviceById(DeviceId id) { + TransportProtos.GetDeviceResponseMsg deviceProto = transportService.getDevice(TransportProtos.GetDeviceRequestMsg.newBuilder() + .setDeviceIdMSB(id.getId().getMostSignificantBits()) + .setDeviceIdLSB(id.getId().getLeastSignificantBits()) + .build()); + + if (deviceProto == null) { + return null; + } + + DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID( + deviceProto.getDeviceProfileIdMSB(), deviceProto.getDeviceProfileIdLSB()) + ); + + Device device = new Device(); + device.setId(id); + device.setDeviceProfileId(deviceProfileId); + + DeviceTransportConfiguration deviceTransportConfiguration = (DeviceTransportConfiguration) dataDecodingEncodingService.decode( + deviceProto.getDeviceTransportConfiguration().toByteArray() + ).orElseThrow(() -> new IllegalStateException("Can't find device transport configuration")); + + DeviceData deviceData = new DeviceData(); + deviceData.setTransportConfiguration(deviceTransportConfiguration); + device.setDeviceData(deviceData); + + return device; + } + + public DeviceCredentials getDeviceCredentialsByDeviceId(DeviceId deviceId) { + TransportProtos.GetDeviceCredentialsResponseMsg deviceCredentialsResponse = transportService.getDeviceCredentials( + TransportProtos.GetDeviceCredentialsRequestMsg.newBuilder() + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .build() + ); + + return (DeviceCredentials) dataDecodingEncodingService.decode(deviceCredentialsResponse.getDeviceCredentialsData().toByteArray()) + .orElseThrow(() -> new IllegalArgumentException("Device credentials not found")); + } + + public List getAllSnmpDevicesIds() { + TransportProtos.GetSnmpDevicesResponseMsg devicesIdsResponse = transportService.getSnmpDevicesIds( + TransportProtos.GetSnmpDevicesRequestMsg.getDefaultInstance() + ); + + return devicesIdsResponse.getIdsList().stream() + .map(UUID::fromString) + .collect(Collectors.toList()); + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportBalancingService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportBalancingService.java new file mode 100644 index 0000000000..3564ab319e --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportBalancingService.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2021 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.snmp.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; +import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.discovery.event.ServiceListChangedEvent; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.event.SnmpTransportListChangedEvent; + +import java.util.Comparator; +import java.util.List; +import java.util.UUID; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@TbSnmpTransportComponent +@Service +@RequiredArgsConstructor +@Slf4j +public class SnmpTransportBalancingService { + private final PartitionService partitionService; + private final ApplicationEventPublisher eventPublisher; + private final SnmpTransportService snmpTransportService; + + private int snmpTransportsCount = 1; + private Integer currentTransportPartitionIndex = 0; + + public void onServiceListChanged(ServiceListChangedEvent event) { + log.trace("Got service list changed event: {}", event); + recalculatePartitions(event.getOtherServices(), event.getCurrentService()); + } + + public boolean isManagedByCurrentTransport(UUID entityId) { + boolean isManaged = resolvePartitionIndexForEntity(entityId) == currentTransportPartitionIndex; + if (!isManaged) { + log.trace("Entity {} is not managed by current SNMP transport node", entityId); + } + return isManaged; + } + + private int resolvePartitionIndexForEntity(UUID entityId) { + return partitionService.resolvePartitionIndex(entityId, snmpTransportsCount); + } + + private void recalculatePartitions(List otherServices, ServiceInfo currentService) { + log.info("Recalculating partitions for SNMP transports"); + List snmpTransports = Stream.concat(otherServices.stream(), Stream.of(currentService)) + .filter(service -> service.getTransportsList().contains(snmpTransportService.getName())) + .sorted(Comparator.comparing(ServiceInfo::getServiceId)) + .collect(Collectors.toList()); + log.trace("Found SNMP transports: {}", snmpTransports); + + int previousCurrentTransportPartitionIndex = currentTransportPartitionIndex; + int previousSnmpTransportsCount = snmpTransportsCount; + + if (!snmpTransports.isEmpty()) { + for (int i = 0; i < snmpTransports.size(); i++) { + if (snmpTransports.get(i).equals(currentService)) { + currentTransportPartitionIndex = i; + break; + } + } + snmpTransportsCount = snmpTransports.size(); + } + + if (snmpTransportsCount != previousSnmpTransportsCount || currentTransportPartitionIndex != previousCurrentTransportPartitionIndex) { + log.info("SNMP transports partitions have changed: transports count = {}, current transport partition index = {}", snmpTransportsCount, currentTransportPartitionIndex); + eventPublisher.publishEvent(new SnmpTransportListChangedEvent()); + } else { + log.info("SNMP transports partitions have not changed"); + } + } + +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/SnmpSessionListener.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java similarity index 60% rename from common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/SnmpSessionListener.java rename to common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 35aa1a9347..22e60c98ee 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/SnmpSessionListener.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.snmp.session; +package org.thingsboard.server.transport.snmp.service; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -25,9 +25,16 @@ import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.event.ResponseEvent; -import org.snmp4j.event.ResponseListener; import org.snmp4j.smi.VariableBinding; +import org.snmp4j.transport.DefaultUdpTransportMapping; +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.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.transport.TransportContext; @@ -39,27 +46,116 @@ import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.snmp.SnmpTransportContext; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; +import javax.annotation.PreDestroy; +import java.io.IOException; import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +@TbSnmpTransportComponent +@Service @Slf4j -@AllArgsConstructor -public class SnmpSessionListener implements ResponseListener { - - @Getter +public class SnmpTransportService implements TbTransportService { private final SnmpTransportContext snmpTransportContext; @Getter - private final String token; + private ExecutorService snmpCallbackExecutor; + @Getter + private Snmp snmp; + private ScheduledExecutorService pollingExecutor; - @Override - public void onResponse(ResponseEvent event) { - ((Snmp) event.getSource()).cancel(event.getRequest(), this); - snmpTransportContext.getSnmpCallbackExecutor().submit(() -> processSnmpResponse(event)); + public SnmpTransportService(@Lazy SnmpTransportContext snmpTransportContext) { + this.snmpTransportContext = snmpTransportContext; } - private void processSnmpResponse(ResponseEvent event) { + // @PostConstruct + private void init() { + log.info("Starting SNMP transport..."); + pollingExecutor = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("snmp-polling")); + //TODO: Set parallelism value in the config + snmpCallbackExecutor = Executors.newWorkStealingPool(20); + + initializeSnmp(); + + log.info("SNMP transport started!"); + } + + @PreDestroy + public void shutdown() { + log.info("Stopping SNMP transport!"); + if (pollingExecutor != null) { + pollingExecutor.shutdownNow(); + } + if (snmpCallbackExecutor != null) { + snmpCallbackExecutor.shutdownNow(); + } + if (snmp != null) { + try { + snmp.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + log.info("SNMP transport stopped!"); + } + + @EventListener(ApplicationReadyEvent.class) + @Order(value = 10) + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + log.info("Received application ready event. Starting SNMP polling."); +// startPolling(); + } + + private void initializeSnmp() { + try { + this.snmp = new Snmp(new DefaultUdpTransportMapping()); + this.snmp.listen(); + } catch (IOException e) { + //TODO: what should be done if transport wasn't initialized? + log.error(e.getMessage(), e); + } + } + + private void startPolling() { + //TODO: Get poll period from configuration; + int pollPeriodSeconds = 1; + + pollingExecutor.scheduleWithFixedDelay(() -> { + snmpTransportContext.getSessions().forEach(this::executeSnmpRequest); + }, 0, pollPeriodSeconds, TimeUnit.SECONDS); + } + + private void executeSnmpRequest(DeviceSessionContext sessionContext) { + long timeNow = System.currentTimeMillis(); + long nextRequestExecutionTime = sessionContext.getPreviousRequestExecutedAt() + sessionContext.getProfileTransportConfiguration().getPollPeriodMs(); + + if (nextRequestExecutionTime < timeNow) { + sessionContext.setPreviousRequestExecutedAt(timeNow); + + DeviceProfileId deviceProfileId = sessionContext.getDeviceProfile().getId(); + snmpTransportContext.getProfilesPdus().get(deviceProfileId).forEach(pdu -> { + try { + log.debug("[{}] Sending SNMP message...", pdu.getRequestID()); + snmp.send(pdu, sessionContext.getTarget(), deviceProfileId, sessionContext); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + }); + } + } + + public void onNewDeviceResponse(ResponseEvent responseEvent, DeviceSessionContext sessionContext) { + ((Snmp) responseEvent.getSource()).cancel(responseEvent.getRequest(), sessionContext); + snmpTransportContext.getSnmpCallbackExecutor().submit(() -> processSnmpResponse(responseEvent, sessionContext)); + } + + private void processSnmpResponse(ResponseEvent event, DeviceSessionContext sessionContext) { PDU response = event.getResponse(); if (event.getError() != null) { log.warn("Response error: {}", event.getError().getMessage(), event.getError()); @@ -72,8 +168,8 @@ public class SnmpSessionListener implements ResponseListener { TransportService transportService = snmpTransportContext.getTransportService(); for (int i = 0; i < response.size(); i++) { VariableBinding vb = response.get(i); - snmpTransportContext.findAttributesMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, - TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(token).build(), + snmpTransportContext.getAttributesMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, + TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(), new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { try { transportService.process(sessionInfo, @@ -84,8 +180,8 @@ public class SnmpSessionListener implements ResponseListener { log.warn("Failed to process SNMP response: {}", e.getMessage(), e); } }))); - snmpTransportContext.findTelemetryMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, - TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(token).build(), + snmpTransportContext.getTelemetryMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, + TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(), new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { try { transportService.process(sessionInfo, @@ -103,6 +199,14 @@ public class SnmpSessionListener implements ResponseListener { } } + private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { + snmpTransportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() + .setAttributeSubscription(false) + .setRpcSubscription(false) + .setLastActivityTime(System.currentTimeMillis()) + .build(), TransportServiceCallback.EMPTY); + } + private TransportProtos.PostAttributeMsg convertToPostAttributes(String keyName, DataType dataType, String payload) throws AdaptorException { try { return JsonConverter.convertToAttributesProto(getKvJson(keyName, dataType, payload)); @@ -143,14 +247,6 @@ public class SnmpSessionListener implements ResponseListener { return new JsonParser().parse(result.toString()); } - private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { - snmpTransportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(false) - .setRpcSubscription(false) - .setLastActivityTime(System.currentTimeMillis()) - .build(), TransportServiceCallback.EMPTY); - } - @AllArgsConstructor private static class DeviceAuthCallback implements TransportServiceCallback { private final TransportContext transportContext; @@ -170,4 +266,9 @@ public class SnmpSessionListener implements ResponseListener { log.warn("Failed to process device auth", e); } } + + @Override + public String getName() { + return "SNMP"; + } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java new file mode 100644 index 0000000000..ed87bbc726 --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -0,0 +1,166 @@ +/** + * Copyright © 2016-2021 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.snmp.session; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.snmp4j.CommunityTarget; +import org.snmp4j.Target; +import org.snmp4j.event.ResponseEvent; +import org.snmp4j.event.ResponseListener; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.OctetString; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; +import org.thingsboard.server.transport.snmp.SnmpTransportContext; +import org.thingsboard.server.transport.snmp.service.SnmpTransportService; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class DeviceSessionContext extends DeviceAwareSessionContext implements SessionMsgListener, ResponseListener { + @Getter + private Target target; + private final String token; + @Getter + @Setter + private SnmpProfileTransportConfiguration profileTransportConfiguration; + @Getter + @Setter + private SnmpDeviceTransportConfiguration deviceTransportConfiguration; + @Getter + private final Device device; + + private final SnmpTransportContext snmpTransportContext; + private final SnmpTransportService snmpTransportService; + + @Getter + @Setter + private long previousRequestExecutedAt = 0; + private final AtomicInteger msgIdSeq = new AtomicInteger(0); + private boolean isActive = true; + + public DeviceSessionContext(Device device, DeviceProfile deviceProfile, + String token, SnmpDeviceTransportConfiguration deviceTransportConfiguration, + SnmpTransportContext snmpTransportContext, SnmpTransportService snmpTransportService) { + super(UUID.randomUUID()); + super.setDeviceId(device.getId()); + super.setDeviceProfile(deviceProfile); + this.device = device; + + this.token = token; + this.snmpTransportContext = snmpTransportContext; + this.snmpTransportService = snmpTransportService; + + this.profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + this.deviceTransportConfiguration = deviceTransportConfiguration; + + initTarget(this.profileTransportConfiguration, this.deviceTransportConfiguration); + } + + @Override + public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) { + super.onDeviceProfileUpdate(newSessionInfo, deviceProfile); + if (isActive) { + snmpTransportContext.onDeviceProfileUpdated(deviceProfile, this); + } + } + + @Override + public void onDeviceDeleted(DeviceId deviceId) { + snmpTransportContext.onDeviceDeleted(this); + } + + @Override + public void onResponse(ResponseEvent event) { + if (isActive) { + snmpTransportService.onNewDeviceResponse(event, this); + } + } + + public void initTarget(SnmpProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + log.trace("Initializing target for SNMP session of device {}", device); + CommunityTarget communityTarget = new CommunityTarget(); + communityTarget.setAddress(GenericAddress.parse(GenericAddress.TYPE_UDP + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); + communityTarget.setVersion(getSnmpVersion(deviceTransportConfig.getProtocolVersion())); + communityTarget.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); + communityTarget.setTimeout(profileTransportConfig.getTimeoutMs()); + communityTarget.setRetries(profileTransportConfig.getRetries()); + this.target = communityTarget; + log.info("SNMP target initialized: {}", this.target); + } + + public void close() { + isActive = false; + } + + public String getToken() { + return token; + } + + //TODO: replace with enum, wtih preliminary discussion of type version in config (string or integer) + private int getSnmpVersion(String configSnmpVersion) { + switch (configSnmpVersion) { + case ("v1"): + return SnmpConstants.version1; + case ("v2c"): + return SnmpConstants.version2c; + case ("v3"): + return SnmpConstants.version3; + default: + return -1; + } + } + + @Override + public int nextMsgId() { + return msgIdSeq.incrementAndGet(); + } + + @Override + public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { + } + + @Override + public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { + } + + @Override + public void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification) { + } + + @Override + public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { + } + + @Override + public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionCtx.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionCtx.java deleted file mode 100644 index 424b964387..0000000000 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionCtx.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright © 2016-2021 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.snmp.session; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.snmp4j.CommunityTarget; -import org.snmp4j.Snmp; -import org.snmp4j.Target; -import org.snmp4j.mp.SnmpConstants; -import org.snmp4j.smi.GenericAddress; -import org.snmp4j.smi.OctetString; -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.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.transport.SessionMsgListener; -import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.auth.SessionInfoCreator; -import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; -import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; -import org.thingsboard.server.transport.snmp.SnmpTransportContext; - -import java.io.IOException; -import java.util.Optional; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Consumer; - -@Slf4j -public class DeviceSessionCtx extends DeviceAwareSessionContext implements SessionMsgListener { - private final AtomicInteger msgIdSeq = new AtomicInteger(0); - - @Getter - @Setter - private SnmpDeviceTransportConfiguration deviceTransportConfig; - @Getter - @Setter - private SnmpSessionListener snmpSessionListener; - @Getter - @Setter - private Target target; - @Getter - @Setter - private volatile TransportProtos.SessionInfoProto sessionInfo; - - private Snmp snmp; - private SnmpProfileTransportConfiguration snmpProfileTransportConfiguration; - private long previousRequestExecutedAt = 0; - - public DeviceSessionCtx(SnmpTransportContext transportContext, String token, SnmpDeviceTransportConfiguration deviceTransportConfig, - Snmp snmp, DeviceId deviceId, DeviceProfile deviceProfile) { - super(UUID.randomUUID()); - this.snmpSessionListener = new SnmpSessionListener(transportContext, token); - super.setDeviceId(deviceId); - super.setDeviceProfile(deviceProfile); - //TODO: What should be done if snmp null? - if (snmp != null) { - this.snmp = snmp; - } - this.snmpProfileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - initTarget(this.snmpProfileTransportConfiguration, deviceTransportConfig); - } - - @Override - public int nextMsgId() { - return msgIdSeq.incrementAndGet(); - } - - @Override - public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { - } - - @Override - public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { - } - - @Override - public void onRemoteSessionCloseCommand(SessionCloseNotificationProto sessionCloseNotification) { - } - - @Override - public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { - } - - @Override - public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { - } - - @Override - public void onDeviceProfileUpdate(TransportProtos.SessionInfoProto newSessionInfo, DeviceProfile deviceProfile) { - super.onDeviceProfileUpdate(sessionInfo, deviceProfile); - if (DeviceTransportType.SNMP.equals(deviceProfile.getTransportType())) { - snmpProfileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - snmpSessionListener.getSnmpTransportContext().getProfileTransportConfig().put( - deviceProfile.getId(), - snmpProfileTransportConfiguration); - snmpSessionListener.getSnmpTransportContext().updatePduListPerProfile(deviceProfile.getId(), snmpProfileTransportConfiguration); - } else { - //TODO: should the context be removed from the map? - } - } - - @Override - public void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { - super.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); - if (super.getDeviceProfile() != null && DeviceTransportType.SNMP.equals(super.getDeviceProfile().getTransportType())) { - snmpSessionListener.getSnmpTransportContext().updateDeviceSessionCtx(device, deviceProfile, null); - SnmpProfileTransportConfiguration profileTransportConfig = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - SnmpDeviceTransportConfiguration deviceTransportConfig = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - initTarget(profileTransportConfig, deviceTransportConfig); - } else { - //TODO: should the context be removed from the map? - } - } - - public void createSessionInfo(Consumer registerSession) { - getSnmpSessionListener().getSnmpTransportContext().getTransportService().process(DeviceTransportType.SNMP, - TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(getSnmpSessionListener().getToken()).build(), - new TransportServiceCallback() { - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (msg.hasDeviceInfo()) { - sessionInfo = SessionInfoCreator.create(msg, getSnmpSessionListener().getSnmpTransportContext(), UUID.randomUUID()); - registerSession.accept(sessionInfo); - setDeviceInfo(msg.getDeviceInfo()); - } else { - log.warn("[{}] Failed to process device auth", getDeviceId()); - } - } - - @Override - public void onError(Throwable e) { - log.warn("[{}] Failed to process device auth", getDeviceId(), e); - } - }); - } - - public void executeSnmpRequest() { - long timeNow = System.currentTimeMillis(); - long nextRequestExecutionTime = previousRequestExecutedAt + snmpProfileTransportConfiguration.getPoolPeriodMs(); - if (nextRequestExecutionTime < timeNow) { - previousRequestExecutedAt = timeNow; - - snmpSessionListener.getSnmpTransportContext().getPdusPerProfile().get(deviceProfile.getId()).forEach(pdu -> { - try { - log.debug("[{}] Sending SNMP message...", pdu.getRequestID()); - snmp.send(pdu, - target, - deviceProfile.getId(), - snmpSessionListener); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - }); - } - } - - private void initTarget(SnmpProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { - this.deviceTransportConfig = deviceTransportConfig; - CommunityTarget communityTarget = new CommunityTarget(); - communityTarget.setAddress(GenericAddress.parse(GenericAddress.TYPE_UDP + ":" + this.deviceTransportConfig.getAddress() + "/" + this.deviceTransportConfig.getPort())); - communityTarget.setVersion(getSnmpVersion(this.deviceTransportConfig.getProtocolVersion())); - communityTarget.setCommunity(new OctetString(this.deviceTransportConfig.getCommunity())); - communityTarget.setTimeout(profileTransportConfig.getTimeoutMs()); - communityTarget.setRetries(profileTransportConfig.getRetries()); - this.target = communityTarget; - log.info("SNMP target initialized: {}", this.target); - } - - //TODO: replace with enum, wtih preliminary discussion of type version in config (string or integer) - private int getSnmpVersion(String configSnmpVersion) { - switch (configSnmpVersion) { - case ("v1"): - return SnmpConstants.version1; - case ("v2c"): - return SnmpConstants.version2c; - case ("v3"): - return SnmpConstants.version3; - default: - return -1; - } - } -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceUpdatedEvent.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceUpdatedEvent.java new file mode 100644 index 0000000000..ad4819fbb1 --- /dev/null +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/DeviceUpdatedEvent.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2021 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.transport; + +import lombok.Getter; +import org.thingsboard.server.common.data.Device; + +@Getter +public class DeviceUpdatedEvent { + private final Device device; + + public DeviceUpdatedEvent(Device device) { + this.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 8209fe2531..9aff642708 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.common.data.id.DeviceId; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -49,4 +50,7 @@ public interface SessionMsgListener { default void onDeviceUpdate(TransportProtos.SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { } + + default void onDeviceDeleted(DeviceId deviceId) { + } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index 75f20cc825..eabcb1e8e6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -21,6 +21,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbTransportComponent; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index ab728af00d..24469dc599 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -22,11 +22,17 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetResourcesRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetResourcesResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.LwM2MRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.LwM2MResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; @@ -55,6 +61,12 @@ public interface TransportService { GetResourcesResponseMsg getResources(GetResourcesRequestMsg msg); + GetSnmpDevicesResponseMsg getSnmpDevicesIds(GetSnmpDevicesRequestMsg requestMsg); + + GetDeviceResponseMsg getDevice(GetDeviceRequestMsg requestMsg); + + GetDeviceCredentialsResponseMsg getDeviceCredentials(GetDeviceCredentialsRequestMsg requestMsg); + void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java index 5c36e9b3e0..ad289a7e16 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportDeviceProfileCache.java @@ -112,8 +112,8 @@ public class DefaultTransportDeviceProfileCache implements TransportDeviceProfil profile = profileOpt.get(); this.put(profile); } else { - log.warn("[{}] Can't device profile: {}", id, entityProfileMsg.getData()); - throw new RuntimeException("Can't device profile!"); + log.warn("[{}] Can't find device profile: {}", id, entityProfileMsg.getData()); + throw new RuntimeException("Can't find device profile!"); } } finally { deviceProfileFetchLock.unlock(); 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 b273ee48cf..903de94020 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 @@ -23,6 +23,7 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; @@ -47,6 +48,7 @@ import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.stats.MessagesStats; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsType; +import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; import org.thingsboard.server.common.transport.TransportService; @@ -61,7 +63,6 @@ import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -131,6 +132,7 @@ public class DefaultTransportService implements TransportService { private final TransportRateLimitService rateLimitService; private final DataDecodingEncodingService dataDecodingEncodingService; private final SchedulerComponent scheduler; + private final ApplicationEventPublisher eventPublisher; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -157,7 +159,8 @@ public class DefaultTransportService implements TransportService { TransportDeviceProfileCache deviceProfileCache, TransportTenantProfileCache tenantProfileCache, TbApiUsageClient apiUsageClient, TransportRateLimitService rateLimitService, - DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { + DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler, + ApplicationEventPublisher eventPublisher) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; this.producerProvider = producerProvider; @@ -169,6 +172,7 @@ public class DefaultTransportService implements TransportService { this.rateLimitService = rateLimitService; this.dataDecodingEncodingService = dataDecodingEncodingService; this.scheduler = scheduler; + this.eventPublisher = eventPublisher; } @PostConstruct @@ -266,6 +270,58 @@ public class DefaultTransportService implements TransportService { } } + @Override + public TransportProtos.GetSnmpDevicesResponseMsg getSnmpDevicesIds(TransportProtos.GetSnmpDevicesRequestMsg requestMsg) { + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>( + UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder() + .setSnmpDevicesRequestMsg(requestMsg) + .build() + ); + + try { + TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); + return response.getValue().getSnmpDevicesResponseMsg(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public TransportProtos.GetDeviceResponseMsg getDevice(TransportProtos.GetDeviceRequestMsg requestMsg) { + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>( + UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder() + .setDeviceRequestMsg(requestMsg) + .build() + ); + + try { + TransportApiResponseMsg response = transportApiRequestTemplate.send(protoMsg).get().getValue(); + if (response.hasDeviceResponseMsg()) { + return response.getDeviceResponseMsg(); + } else { + return null; + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + @Override + public TransportProtos.GetDeviceCredentialsResponseMsg getDeviceCredentials(TransportProtos.GetDeviceCredentialsRequestMsg requestMsg) { + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>( + UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder() + .setDeviceCredentialsRequestMsg(requestMsg) + .build() + ); + + try { + TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); + return response.getValue().getDeviceCredentialsResponseMsg(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + @Override public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { @@ -685,7 +741,10 @@ public class DefaultTransportService implements TransportService { } } else if (EntityType.DEVICE.equals(entityType)) { Optional deviceOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); - deviceOpt.ifPresent(this::onDeviceUpdate); + deviceOpt.ifPresent(device -> { + onDeviceUpdate(device); + eventPublisher.publishEvent(new DeviceUpdatedEvent(device)); + }); } } else if (toSessionMsg.hasEntityDeleteMsg()) { TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); @@ -699,6 +758,7 @@ public class DefaultTransportService implements TransportService { rateLimitService.remove(new TenantId(entityUuid)); } else if (EntityType.DEVICE.equals(entityType)) { rateLimitService.remove(new DeviceId(entityUuid)); + onDeviceDeleted(new DeviceId(entityUuid)); } } else if (toSessionMsg.hasResourceUpdateMsg()) { //TODO: update resource cache @@ -764,6 +824,17 @@ public class DefaultTransportService implements TransportService { }); } + private void onDeviceDeleted(DeviceId deviceId) { + sessions.forEach((id, md) -> { + DeviceId sessionDeviceId = new DeviceId(new UUID(md.getSessionInfo().getDeviceIdMSB(), md.getSessionInfo().getDeviceIdLSB())); + if (sessionDeviceId.equals(deviceId)) { + transportCallbackExecutor.submit(() -> { + md.getListener().onDeviceDeleted(deviceId); + }); + } + }); + } + protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 7f8d512c70..547725b635 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.device; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -215,4 +216,7 @@ public interface DeviceDao extends Dao, TenantEntityDao { * @return the list of device objects */ PageData findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink); + + List findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index f933cbd543..64b44db686 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; @@ -80,6 +81,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -554,6 +556,11 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return savedDevice; } + @Override + public List findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType) { + return deviceDao.findDevicesIdsByDeviceProfileTransportType(transportType); + } + private DataValidator deviceValidator = new DataValidator() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index c39e481638..708e0f38e3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.dao.model.sql.DeviceEntity; import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; @@ -170,4 +171,9 @@ public interface DeviceRepository extends PagingAndSortingRepository findIdsByDeviceProfileTransportType(@Param("transportType") DeviceTransportType transportType); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 18979eba91..cb91ffac4f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; @@ -114,6 +115,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao DaoUtil.toPageable(pageLink))); } + @Override + public List findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType) { + return deviceRepository.findIdsByDeviceProfileTransportType(transportType); + } + @Override public PageData findDeviceInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { return DaoUtil.toPageData( diff --git a/docker/tb-transports/snmp/conf/logback.xml b/docker/tb-transports/snmp/conf/logback.xml new file mode 100644 index 0000000000..e581a97c42 --- /dev/null +++ b/docker/tb-transports/snmp/conf/logback.xml @@ -0,0 +1,50 @@ + + + + + + + /var/log/tb-snmp-transport/${TB_SERVICE_ID}/tb-snmp-transport.log + + /var/log/tb-snmp-transport/${TB_SERVICE_ID}/tb-snmp-transport.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/docker/tb-transports/snmp/conf/tb-snmp-transport.conf b/docker/tb-transports/snmp/conf/tb-snmp-transport.conf new file mode 100644 index 0000000000..2aceae797c --- /dev/null +++ b/docker/tb-transports/snmp/conf/tb-snmp-transport.conf @@ -0,0 +1,23 @@ +# +# Copyright © 2016-2021 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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=/var/log/tb-snmp-transport/${TB_SERVICE_ID}-gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-snmp-transport/${TB_SERVICE_ID}-heapdump.bin" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export JAVA_OPTS="$JAVA_OPTS -XX:+ExitOnOutOfMemoryError" +export LOG_FILENAME=tb-snmp-transport.out +export LOADER_PATH=/usr/share/tb-snmp-transport/conf diff --git a/msa/transport/snmp/docker/Dockerfile b/msa/transport/snmp/docker/Dockerfile index e39624d538..b808932482 100644 --- a/msa/transport/snmp/docker/Dockerfile +++ b/msa/transport/snmp/docker/Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # -FROM thingsboard/openjdk8 +FROM thingsboard/openjdk11 COPY start-tb-snmp-transport.sh ${pkg.name}.deb /tmp/ diff --git a/transport/snmp/src/main/conf/logback.xml b/transport/snmp/src/main/conf/logback.xml new file mode 100644 index 0000000000..d9818c2257 --- /dev/null +++ b/transport/snmp/src/main/conf/logback.xml @@ -0,0 +1,45 @@ + + + + + + + ${pkg.logFolder}/${pkg.name}.log + + ${pkg.logFolder}/${pkg.name}.%d{yyyy-MM-dd}.%i.log + 100MB + 30 + 3GB + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + diff --git a/transport/snmp/src/main/conf/tb-snmp-transport.conf b/transport/snmp/src/main/conf/tb-snmp-transport.conf new file mode 100644 index 0000000000..73b6e04f68 --- /dev/null +++ b/transport/snmp/src/main/conf/tb-snmp-transport.conf @@ -0,0 +1,22 @@ +# +# Copyright © 2016-2021 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. +# + +export JAVA_OPTS="$JAVA_OPTS -Xlog:gc*,heap*,age*,safepoint=debug:file=@pkg.logFolder@/gc.log:time,uptime,level,tags:filecount=10,filesize=10M" +export JAVA_OPTS="$JAVA_OPTS -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError" +export JAVA_OPTS="$JAVA_OPTS -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark" +export JAVA_OPTS="$JAVA_OPTS -XX:+UseG1GC -XX:MaxGCPauseMillis=500 -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:MaxTenuringThreshold=10" +export LOG_FILENAME=${pkg.name}.out +export LOADER_PATH=${pkg.installFolder}/conf diff --git a/transport/snmp/src/main/resources/logback.xml b/transport/snmp/src/main/resources/logback.xml new file mode 100644 index 0000000000..7a3d9f6f0d --- /dev/null +++ b/transport/snmp/src/main/resources/logback.xml @@ -0,0 +1,36 @@ + + + + + + + + %d{ISO8601} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + + \ No newline at end of file diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml new file mode 100644 index 0000000000..9f6bd131ac --- /dev/null +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -0,0 +1,394 @@ +# +# Copyright © 2016-2021 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. +# + +server: + # Server bind address + address: "${HTTP_BIND_ADDRESS:0.0.0.0}" + # Server bind port + port: "${HTTP_BIND_PORT:8085}" + # Server SSL configuration + +# Zookeeper connection parameters. Used for service discovery. +zk: + # Enable/disable zookeeper discovery service. + enabled: "${ZOOKEEPER_ENABLED:false}" + # Zookeeper connect string + url: "${ZOOKEEPER_URL:localhost:2181}" + # Zookeeper retry interval in milliseconds + retry_interval_ms: "${ZOOKEEPER_RETRY_INTERVAL_MS:3000}" + # Zookeeper connection timeout in milliseconds + connection_timeout_ms: "${ZOOKEEPER_CONNECTION_TIMEOUT_MS:3000}" + # Zookeeper session timeout in milliseconds + session_timeout_ms: "${ZOOKEEPER_SESSION_TIMEOUT_MS:3000}" + # Name of the directory in zookeeper 'filesystem' + zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" + +cluster: + stats: + enabled: "${TB_CLUSTER_STATS_ENABLED:false}" + print_interval_ms: "${TB_CLUSTER_STATS_PRINT_INTERVAL_MS:10000}" + +cache: + # caffeine or redis + type: "${CACHE_TYPE:caffeine}" + attributes: + # make sure that if cache.type is 'redis' and cache.attributes.enabled is 'true' that you change 'maxmemory-policy' Redis config property to 'allkeys-lru', 'allkeys-lfu' or 'allkeys-random' + enabled: "${CACHE_ATTRIBUTES_ENABLED:true}" + +caffeine: + specs: + relations: + timeToLiveInMinutes: 1440 + maxSize: 0 + deviceCredentials: + timeToLiveInMinutes: 1440 + maxSize: 0 + devices: + timeToLiveInMinutes: 1440 + maxSize: 0 + sessions: + timeToLiveInMinutes: 1440 + maxSize: 0 + assets: + timeToLiveInMinutes: 1440 + maxSize: 0 + entityViews: + timeToLiveInMinutes: 1440 + maxSize: 0 + claimDevices: + timeToLiveInMinutes: 1 + maxSize: 0 + securitySettings: + timeToLiveInMinutes: 1440 + maxSize: 0 + tenantProfiles: + timeToLiveInMinutes: 1440 + maxSize: 0 + deviceProfiles: + timeToLiveInMinutes: 1440 + maxSize: 0 + attributes: + timeToLiveInMinutes: 1440 + maxSize: 100000 + +redis: + # standalone or cluster + connection: + type: "${REDIS_CONNECTION_TYPE:standalone}" + standalone: + host: "${REDIS_HOST:localhost}" + port: "${REDIS_PORT:6379}" + useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}" + # this value may be used only if you used not default ClientConfig + clientName: "${REDIS_CLIENT_NAME:standalone}" + # this value may be used only if you used not default ClientConfig + connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}" + # this value may be used only if you used not default ClientConfig + readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" + # this value may be used only if you used not default ClientConfig + usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}" + cluster: + # Comma-separated list of "host:port" pairs to bootstrap from. + nodes: "${REDIS_NODES:}" + # Maximum number of redirects to follow when executing commands across the cluster. + max-redirects: "${REDIS_MAX_REDIRECTS:12}" + useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}" + # db index + db: "${REDIS_DB:0}" + # db password + password: "${REDIS_PASSWORD:}" + # pool config + pool_config: + maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" + maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}" + minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}" + testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}" + testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}" + testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}" + minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}" + evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}" + maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}" + numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}" + blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" + +# Check new version updates parameters +updates: + # Enable/disable updates checking. + enabled: "${UPDATES_ENABLED:true}" + +# spring freemarker configuration +spring.freemarker.checkTemplateLocation: "false" + +audit-log: + # Enable/disable audit log functionality. + enabled: "${AUDIT_LOG_ENABLED:true}" + # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS + by_tenant_partitioning: "${AUDIT_LOG_BY_TENANT_PARTITIONING:MONTHS}" + # Number of days as history period if startTime and endTime are not specified + default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" + # Logging levels per each entity type. + # Allowed values: OFF (disable), W (log write operations), RW (log read and write operations) + logging-level: + mask: + "device": "${AUDIT_LOG_MASK_DEVICE:W}" + "asset": "${AUDIT_LOG_MASK_ASSET:W}" + "dashboard": "${AUDIT_LOG_MASK_DASHBOARD:W}" + "customer": "${AUDIT_LOG_MASK_CUSTOMER:W}" + "user": "${AUDIT_LOG_MASK_USER:W}" + "rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}" + "alarm": "${AUDIT_LOG_MASK_ALARM:W}" + "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}" + "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}" + sink: + # Type of external sink. possible options: none, elasticsearch + type: "${AUDIT_LOG_SINK_TYPE:none}" + # Name of the index where audit logs stored + # Index name could contain next placeholders (not mandatory): + # @{TENANT} - substituted by tenant ID + # @{DATE} - substituted by current date in format provided in audit_log.sink.date_format + index_pattern: "${AUDIT_LOG_SINK_INDEX_PATTERN:@{TENANT}_AUDIT_LOG_@{DATE}}" + # Date format. Details of the pattern could be found here: + # https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html + date_format: "${AUDIT_LOG_SINK_DATE_FORMAT:YYYY.MM.DD}" + scheme_name: "${AUDIT_LOG_SINK_SCHEME_NAME:http}" # http or https + host: "${AUDIT_LOG_SINK_HOST:localhost}" + port: "${AUDIT_LOG_SINK_PORT:9200}" + user_name: "${AUDIT_LOG_SINK_USER_NAME:}" + password: "${AUDIT_LOG_SINK_PASSWORD:}" + +state: + # Should be greater then transport.sessions.report_timeout + defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:600}" + defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}" + persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}" + +transport: + sessions: + inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" + report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" + json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" + # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) + max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" + # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) + api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" + # Local LwM2M transport parameters + snmp: + enabled: "${SNMP_ENABLED:true}" + +queue: + type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) + in_memory: + stats: + # For debug lvl + print-interval-ms: "${TB_QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}" + kafka: + bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" + acks: "${TB_KAFKA_ACKS:all}" + retries: "${TB_KAFKA_RETRIES:1}" + batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" + linger.ms: "${TB_KAFKA_LINGER_MS:1}" + buffer.memory: "${TB_BUFFER_MEMORY:33554432}" + replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" + max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}" + max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}" + max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}" + fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}" + use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" + confluent: + ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}" + sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" + sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" + security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" + other: + topic-properties: + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}" + consumer-stats: + enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}" + print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}" + kafka-response-timeout-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_RESPONSE_TIMEOUT_MS:1000}" + aws_sqs: + use_default_credential_provider_chain: "${TB_QUEUE_AWS_SQS_USE_DEFAULT_CREDENTIAL_PROVIDER_CHAIN:false}" + access_key_id: "${TB_QUEUE_AWS_SQS_ACCESS_KEY_ID:YOUR_KEY}" + secret_access_key: "${TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY:YOUR_SECRET}" + region: "${TB_QUEUE_AWS_SQS_REGION:YOUR_REGION}" + threads_per_topic: "${TB_QUEUE_AWS_SQS_THREADS_PER_TOPIC:1}" + queue-properties: + rule-engine: "${TB_QUEUE_AWS_SQS_RE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" + core: "${TB_QUEUE_AWS_SQS_CORE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" + transport-api: "${TB_QUEUE_AWS_SQS_TA_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" + notifications: "${TB_QUEUE_AWS_SQS_NOTIFICATIONS_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" + js-executor: "${TB_QUEUE_AWS_SQS_JE_QUEUE_PROPERTIES:VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800}" + # VisibilityTimeout in seconds;MaximumMessageSize in bytes;MessageRetentionPeriod in seconds + pubsub: + project_id: "${TB_QUEUE_PUBSUB_PROJECT_ID:YOUR_PROJECT_ID}" + service_account: "${TB_QUEUE_PUBSUB_SERVICE_ACCOUNT:YOUR_SERVICE_ACCOUNT}" + max_msg_size: "${TB_QUEUE_PUBSUB_MAX_MSG_SIZE:1048576}" #in bytes + max_messages: "${TB_QUEUE_PUBSUB_MAX_MESSAGES:1000}" + queue-properties: + rule-engine: "${TB_QUEUE_PUBSUB_RE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" + core: "${TB_QUEUE_PUBSUB_CORE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" + transport-api: "${TB_QUEUE_PUBSUB_TA_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" + notifications: "${TB_QUEUE_PUBSUB_NOTIFICATIONS_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" + js-executor: "${TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES:ackDeadlineInSec:30;messageRetentionInSec:604800}" + service_bus: + namespace_name: "${TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME:YOUR_NAMESPACE_NAME}" + sas_key_name: "${TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME:YOUR_SAS_KEY_NAME}" + sas_key: "${TB_QUEUE_SERVICE_BUS_SAS_KEY:YOUR_SAS_KEY}" + max_messages: "${TB_QUEUE_SERVICE_BUS_MAX_MESSAGES:1000}" + queue-properties: + rule-engine: "${TB_QUEUE_SERVICE_BUS_RE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" + core: "${TB_QUEUE_SERVICE_BUS_CORE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" + transport-api: "${TB_QUEUE_SERVICE_BUS_TA_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" + notifications: "${TB_QUEUE_SERVICE_BUS_NOTIFICATIONS_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" + js-executor: "${TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES:lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800}" + rabbitmq: + exchange_name: "${TB_QUEUE_RABBIT_MQ_EXCHANGE_NAME:}" + host: "${TB_QUEUE_RABBIT_MQ_HOST:localhost}" + port: "${TB_QUEUE_RABBIT_MQ_PORT:5672}" + virtual_host: "${TB_QUEUE_RABBIT_MQ_VIRTUAL_HOST:/}" + username: "${TB_QUEUE_RABBIT_MQ_USERNAME:YOUR_USERNAME}" + password: "${TB_QUEUE_RABBIT_MQ_PASSWORD:YOUR_PASSWORD}" + automatic_recovery_enabled: "${TB_QUEUE_RABBIT_MQ_AUTOMATIC_RECOVERY_ENABLED:false}" + connection_timeout: "${TB_QUEUE_RABBIT_MQ_CONNECTION_TIMEOUT:60000}" + handshake_timeout: "${TB_QUEUE_RABBIT_MQ_HANDSHAKE_TIMEOUT:10000}" + queue-properties: + rule-engine: "${TB_QUEUE_RABBIT_MQ_RE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" + core: "${TB_QUEUE_RABBIT_MQ_CORE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" + transport-api: "${TB_QUEUE_RABBIT_MQ_TA_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" + notifications: "${TB_QUEUE_RABBIT_MQ_NOTIFICATIONS_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" + js-executor: "${TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" + partitions: + hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256 + transport_api: + requests_topic: "${TB_QUEUE_TRANSPORT_API_REQUEST_TOPIC:tb_transport.api.requests}" + responses_topic: "${TB_QUEUE_TRANSPORT_API_RESPONSE_TOPIC:tb_transport.api.responses}" + max_pending_requests: "${TB_QUEUE_TRANSPORT_MAX_PENDING_REQUESTS:10000}" + max_requests_timeout: "${TB_QUEUE_TRANSPORT_MAX_REQUEST_TIMEOUT:10000}" + max_callback_threads: "${TB_QUEUE_TRANSPORT_MAX_CALLBACK_THREADS:100}" + request_poll_interval: "${TB_QUEUE_TRANSPORT_REQUEST_POLL_INTERVAL_MS:25}" + response_poll_interval: "${TB_QUEUE_TRANSPORT_RESPONSE_POLL_INTERVAL_MS:25}" + core: + topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" + poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" + partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" + pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" + stats: + enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" + print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}" + js: + # JS Eval request topic + request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}" + # JS Eval responses topic prefix that is combined with node id + response_topic_prefix: "${REMOTE_JS_EVAL_RESPONSE_TOPIC:js_eval.responses}" + # JS Eval max pending requests + max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}" + # JS Eval max request timeout + max_eval_requests_timeout: "${REMOTE_JS_MAX_EVAL_REQUEST_TIMEOUT:60000}" + # JS max request timeout + max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" + # JS response poll interval + response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" + rule-engine: + topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" + poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" + pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:2000}" + stats: + enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}" + print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}" + queues: + - name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}" + topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}" + poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}" + partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}" + pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:2000}" + submit-strategy: + type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL + # For BATCH only + batch-size: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_BATCH_SIZE:1000}" # Maximum number of messages in batch + processing-strategy: + type: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_TYPE:SKIP_ALL_FAILURES}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT + retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited + failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; + pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries; + max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}"# Max allowed time in seconds for pause between retries. + - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}" + topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" + poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" + partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}" + pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:2000}" + submit-strategy: + type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL + # For BATCH only + batch-size: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch + processing-strategy: + type: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT + retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited + failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; + pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; + max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries. + - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}" + topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" + poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" + partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}" + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:2000}" + submit-strategy: + type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL + # For BATCH only + batch-size: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_BATCH_SIZE:100}" # Maximum number of messages in batch + processing-strategy: + type: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_TYPE:RETRY_FAILED_AND_TIMED_OUT}" # SKIP_ALL_FAILURES, RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT + # For RETRY_ALL, RETRY_FAILED, RETRY_TIMED_OUT, RETRY_FAILED_AND_TIMED_OUT + retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited + failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; + pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; + max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries. + transport: + # For high priority notifications that require minimum latency and processing time + notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" + poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" + +event: + debug: + max-symbols: "${TB_MAX_DEBUG_EVENT_SYMBOLS:4096}" + +service: + type: "${TB_SERVICE_TYPE:tb-transport}" + # Unique id for this service (autogenerated if empty) + id: "${TB_SERVICE_ID:}" + tenant_id: "${TB_SERVICE_TENANT_ID:}" # empty or specific tenant id. + +metrics: + # Enable/disable actuator metrics. + enabled: "${METRICS_ENABLED:false}" + timer: + # Metrics percentiles returned by actuator for timer metrics. List of double values (divided by ,). + percentiles: "${METRICS_TIMER_PERCENTILES:0.5}" + +management: + endpoints: + web: + exposure: + # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics). + include: '${METRICS_ENDPOINTS_EXPOSE:info}' From 0532d22d9dfeede30fd206b41ea1398629c654e5 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 24 Mar 2021 12:05:43 +0200 Subject: [PATCH 004/127] Refactor --- ...ProfileKvMapping.java => SnmpMapping.java} | 9 +- .../SnmpProfileTransportConfiguration.java | 10 +- .../DefaultTbServiceInfoProvider.java | 5 +- .../server/queue/util/AfterContextReady.java | 35 +++ .../server/queue/util/AfterStartUp.java | 35 +++ .../transport/snmp/SnmpTransportContext.java | 31 +-- .../snmp/service/SnmpTransportService.java | 237 +++++++----------- 7 files changed, 184 insertions(+), 178 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{SnmpDeviceProfileKvMapping.java => SnmpMapping.java} (90%) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java similarity index 90% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java index 6eff7cbd34..6bd0ca6b4d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java @@ -18,11 +18,10 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; import org.thingsboard.server.common.data.kv.DataType; -//TODO: rename class @Data -public class SnmpDeviceProfileKvMapping { - private String key; - private DataType type; - private String method; +public class SnmpMapping { private String oid; + private String method; + private String key; + private DataType dataType; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java index 50f6c4351c..fb6b46fbb6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java @@ -29,8 +29,8 @@ public class SnmpProfileTransportConfiguration implements DeviceProfileTransport private int pollPeriodMs; private int timeoutMs; private int retries; - private List attributes; - private List telemetry; + private List attributesMappings; + private List telemetryMappings; @Override public DeviceTransportType getType() { @@ -38,9 +38,9 @@ public class SnmpProfileTransportConfiguration implements DeviceProfileTransport } @JsonIgnore - public List getKvMappings() { - if (attributes != null && telemetry != null) { - return Stream.concat(attributes.stream(), telemetry.stream()).collect(Collectors.toList()); + public List getAllMappings() { + if (attributesMappings != null && telemetryMappings != null) { + return Stream.concat(attributesMappings.stream(), telemetryMappings.stream()).collect(Collectors.toList()); } else { return Collections.emptyList(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java index 3ba360cd8e..6988bef9e1 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java @@ -20,8 +20,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.TbTransportService; @@ -31,6 +29,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; +import org.thingsboard.server.queue.util.AfterContextReady; import javax.annotation.PostConstruct; import java.net.InetAddress; @@ -109,7 +108,7 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider { serviceInfo = builder.build(); } - @EventListener(ContextRefreshedEvent.class) + @AfterContextReady public void setTransports() { serviceInfo = ServiceInfo.newBuilder(serviceInfo) .addAllTransports(getTransportServices().stream() diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java new file mode 100644 index 0000000000..5c68a7609a --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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.queue.util; + +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.Order; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@EventListener(ContextRefreshedEvent.class) +@Order +public @interface AfterContextReady { + @AliasFor(annotation = Order.class, attribute = "value") + int order() default Integer.MAX_VALUE; +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java new file mode 100644 index 0000000000..5d2e94ee68 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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.queue.util; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.Order; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@EventListener(ApplicationReadyEvent.class) +@Order +public @interface AfterStartUp { + @AliasFor(annotation = Order.class, attribute = "value") + int order() default Integer.MAX_VALUE; +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index a9fa8380fa..df2582005a 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -20,16 +20,14 @@ import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.smi.OID; import org.snmp4j.smi.VariableBinding; -import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; 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.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileKvMapping; +import org.thingsboard.server.common.data.device.profile.SnmpMapping; import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -45,6 +43,7 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.service.ProtoTransportEntityService; import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingService; @@ -61,7 +60,6 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; @TbSnmpTransportComponent @@ -80,8 +78,7 @@ public class SnmpTransportContext extends TransportContext { private final Map> profilesPdus = new ConcurrentHashMap<>(); private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); - @EventListener(ApplicationReadyEvent.class) - @Order(2) + @AfterStartUp(order = 2) public void initDevicesSessions() { log.info("Initializing SNMP devices sessions"); allSnmpDevicesIds = protoEntityService.getAllSnmpDevicesIds().stream() @@ -209,17 +206,17 @@ public class SnmpTransportContext extends TransportContext { } private List createPdus(SnmpProfileTransportConfiguration deviceProfileConfig) { - Map> varBindingPerMethod = new HashMap<>(); + Map> bindingsPerMethod = new HashMap<>(); - deviceProfileConfig.getKvMappings().forEach(mapping -> varBindingPerMethod + deviceProfileConfig.getAllMappings().forEach(mapping -> bindingsPerMethod .computeIfAbsent(mapping.getMethod(), v -> new ArrayList<>()) .add(new VariableBinding(new OID(mapping.getOid())))); - return varBindingPerMethod.keySet().stream() + return bindingsPerMethod.keySet().stream() .map(method -> { PDU request = new PDU(); request.setType(getSnmpMethod(method)); - request.addAll(varBindingPerMethod.get(method)); + request.addAll(bindingsPerMethod.get(method)); return request; }) .collect(Collectors.toList()); @@ -300,31 +297,27 @@ public class SnmpTransportContext extends TransportContext { return profilesPdus; } - public Optional getAttributesMapping(DeviceProfileId deviceProfileId, OID responseOid) { + public Optional getAttributeMapping(DeviceProfileId deviceProfileId, OID responseOid) { if (profilesTransportConfigs.containsKey(deviceProfileId)) { - return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getAttributes()); + return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getAttributesMappings()); } return Optional.empty(); } - public Optional getTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { + public Optional getTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { if (profilesTransportConfigs.containsKey(deviceProfileId)) { - return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getTelemetry()); + return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getTelemetryMappings()); } return Optional.empty(); } - private Optional getMapping(OID responseOid, List mappings) { + private Optional getMapping(OID responseOid, List mappings) { return mappings.stream() .filter(kvMapping -> new OID(kvMapping.getOid()).equals(responseOid)) //TODO: OID shouldn't be duplicated in the config, add backend and UI verification .findFirst(); } - public ExecutorService getSnmpCallbackExecutor() { - return snmpTransportService.getSnmpCallbackExecutor(); - } - private int getSnmpMethod(String configMethod) { switch (configMethod) { case "get": diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 22e60c98ee..3e9eb63943 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -15,114 +15,74 @@ */ package org.thingsboard.server.transport.snmp.service; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; -import lombok.AllArgsConstructor; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.event.ResponseEvent; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; -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.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.auth.SessionInfoCreator; -import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.snmp.SnmpTransportContext; +import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; +import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; -import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; @TbSnmpTransportComponent @Service @Slf4j public class SnmpTransportService implements TbTransportService { private final SnmpTransportContext snmpTransportContext; + private final TransportService transportService; - @Getter - private ExecutorService snmpCallbackExecutor; - @Getter private Snmp snmp; private ScheduledExecutorService pollingExecutor; + private ExecutorService snmpResponseProcessingExecutor; - public SnmpTransportService(@Lazy SnmpTransportContext snmpTransportContext) { + public SnmpTransportService(@Lazy SnmpTransportContext snmpTransportContext, + TransportService transportService) { this.snmpTransportContext = snmpTransportContext; + this.transportService = transportService; } - // @PostConstruct - private void init() { - log.info("Starting SNMP transport..."); + @PostConstruct + private void init() throws IOException { + log.info("Initializing SNMP transport service"); + pollingExecutor = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("snmp-polling")); //TODO: Set parallelism value in the config - snmpCallbackExecutor = Executors.newWorkStealingPool(20); + snmpResponseProcessingExecutor = Executors.newWorkStealingPool(20); initializeSnmp(); - log.info("SNMP transport started!"); - } - - @PreDestroy - public void shutdown() { - log.info("Stopping SNMP transport!"); - if (pollingExecutor != null) { - pollingExecutor.shutdownNow(); - } - if (snmpCallbackExecutor != null) { - snmpCallbackExecutor.shutdownNow(); - } - if (snmp != null) { - try { - snmp.close(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } - log.info("SNMP transport stopped!"); - } - - @EventListener(ApplicationReadyEvent.class) - @Order(value = 10) - public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { - log.info("Received application ready event. Starting SNMP polling."); -// startPolling(); + log.info("SNMP transport service initialized"); } - private void initializeSnmp() { - try { - this.snmp = new Snmp(new DefaultUdpTransportMapping()); - this.snmp.listen(); - } catch (IOException e) { - //TODO: what should be done if transport wasn't initialized? - log.error(e.getMessage(), e); - } + private void initializeSnmp() throws IOException { + snmp = new Snmp(new DefaultUdpTransportMapping()); + snmp.listen(); } - private void startPolling() { + @AfterStartUp(order = 10) + public void startPolling() { + log.info("Starting SNMP polling"); //TODO: Get poll period from configuration; int pollPeriodSeconds = 1; @@ -141,7 +101,7 @@ public class SnmpTransportService implements TbTransportService { DeviceProfileId deviceProfileId = sessionContext.getDeviceProfile().getId(); snmpTransportContext.getProfilesPdus().get(deviceProfileId).forEach(pdu -> { try { - log.debug("[{}] Sending SNMP message...", pdu.getRequestID()); + log.debug("[{}] Sending SNMP message for device {}", pdu.getRequestID(), sessionContext.getDeviceId()); snmp.send(pdu, sessionContext.getTarget(), deviceProfileId, sessionContext); } catch (IOException e) { log.error(e.getMessage(), e); @@ -152,118 +112,84 @@ public class SnmpTransportService implements TbTransportService { public void onNewDeviceResponse(ResponseEvent responseEvent, DeviceSessionContext sessionContext) { ((Snmp) responseEvent.getSource()).cancel(responseEvent.getRequest(), sessionContext); - snmpTransportContext.getSnmpCallbackExecutor().submit(() -> processSnmpResponse(responseEvent, sessionContext)); + snmpResponseProcessingExecutor.submit(() -> processSnmpResponse(responseEvent, sessionContext)); } private void processSnmpResponse(ResponseEvent event, DeviceSessionContext sessionContext) { - PDU response = event.getResponse(); if (event.getError() != null) { log.warn("Response error: {}", event.getError().getMessage(), event.getError()); + return; + } + + PDU response = event.getResponse(); + if (response == null) { + log.warn("No SNMP response, requestId: {}", event.getRequest().getRequestID()); + return; } - if (response != null) { - log.debug("[{}] Processing SNMP response: {}", response.getRequestID(), response); + DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); + log.debug("[{}] Processing SNMP response for device {} with device profile {}: {}", + response.getRequestID(), sessionContext.getDeviceId(), deviceProfileId, response); - DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); - TransportService transportService = snmpTransportContext.getTransportService(); - for (int i = 0; i < response.size(); i++) { - VariableBinding vb = response.get(i); - snmpTransportContext.getAttributesMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, - TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(), - new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { - try { - transportService.process(sessionInfo, - convertToPostAttributes(kvMapping.getKey(), kvMapping.getType(), vb.toValueString()), - TransportServiceCallback.EMPTY); - reportActivity(sessionInfo); - } catch (Exception e) { - log.warn("Failed to process SNMP response: {}", e.getMessage(), e); - } - }))); - snmpTransportContext.getTelemetryMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, - TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(), - new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { - try { - transportService.process(sessionInfo, - convertToPostTelemetry(kvMapping.getKey(), kvMapping.getType(), vb.toValueString()), - TransportServiceCallback.EMPTY); - reportActivity(sessionInfo); + JsonObject telemetry = new JsonObject(); + JsonObject attributes = new JsonObject(); - } catch (Exception e) { - log.warn("Failed to process SNMP response: {}", e.getMessage(), e); - } - }))); - } - } else { - log.warn("No SNMP response, requestId: {}", event.getRequest().getRequestID()); + for (int i = 0; i < response.size(); i++) { + VariableBinding variableBinding = response.get(i); + log.trace("Processing variable binding {}: {}", i, variableBinding); + + snmpTransportContext.getTelemetryMapping(deviceProfileId, variableBinding.getOid()).ifPresent(mapping -> { + log.trace("Found telemetry mapping for oid {}: {}", variableBinding.getOid(), mapping); + processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), telemetry); + }); + + snmpTransportContext.getAttributeMapping(deviceProfileId, variableBinding.getOid()).ifPresent(mapping -> { + log.trace("Found attribute mapping for oid {}: {}", variableBinding.getOid(), mapping); + processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), attributes); + }); + } + + if (telemetry.entrySet().isEmpty() && attributes.entrySet().isEmpty()) { + log.warn("No telemetry or attribute values is the SNMP response for device {}", sessionContext.getDeviceId()); + return; } + + if (!telemetry.entrySet().isEmpty()) { + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(telemetry); + transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); + log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), telemetry); + } + + if (!attributes.entrySet().isEmpty()) { + TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(attributes); + transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); + log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), attributes); + } + + reportActivity(sessionContext.getSessionInfo()); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { - snmpTransportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() + transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(false) .setRpcSubscription(false) .setLastActivityTime(System.currentTimeMillis()) .build(), TransportServiceCallback.EMPTY); } - private TransportProtos.PostAttributeMsg convertToPostAttributes(String keyName, DataType dataType, String payload) throws AdaptorException { - try { - return JsonConverter.convertToAttributesProto(getKvJson(keyName, dataType, payload)); - } catch (IllegalStateException | JsonSyntaxException ex) { - //TODO: change the exception type - throw new AdaptorException(ex); - } - } - - private TransportProtos.PostTelemetryMsg convertToPostTelemetry(String keyName, DataType dataType, String payload) throws AdaptorException { - try { - return JsonConverter.convertToTelemetryProto(getKvJson(keyName, dataType, payload)); - } catch (IllegalStateException | JsonSyntaxException ex) { - //TODO: change the exception type - throw new AdaptorException(ex); - } - } - - private JsonElement getKvJson(String keyName, DataType dataType, String payload) throws AdaptorException { - JsonObject result = new JsonObject(); + private void processValue(String key, DataType dataType, String value, JsonObject result) { switch (dataType) { case LONG: - result.addProperty(keyName, Long.parseLong(payload)); + result.addProperty(key, Long.parseLong(value)); break; case BOOLEAN: - result.addProperty(keyName, Boolean.parseBoolean(payload)); + result.addProperty(key, Boolean.parseBoolean(value)); break; case DOUBLE: - result.addProperty(keyName, Double.parseDouble(payload)); - break; - case STRING: - result.addProperty(keyName, payload); + result.addProperty(key, Double.parseDouble(value)); break; default: - //TODO: change the exception type - throw new AdaptorException("Unsupported data type"); - } - return new JsonParser().parse(result.toString()); - } - - @AllArgsConstructor - private static class DeviceAuthCallback implements TransportServiceCallback { - private final TransportContext transportContext; - private final Consumer onSuccess; - - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (msg.hasDeviceInfo()) { - onSuccess.accept(SessionInfoCreator.create(msg, transportContext, UUID.randomUUID())); - } else { - log.warn("Failed to process device auth"); - } - } - - @Override - public void onError(Throwable e) { - log.warn("Failed to process device auth", e); + result.addProperty(key, value); } } @@ -271,4 +197,23 @@ public class SnmpTransportService implements TbTransportService { public String getName() { return "SNMP"; } + + @PreDestroy + public void shutdown() { + log.info("Stopping SNMP transport!"); + if (pollingExecutor != null) { + pollingExecutor.shutdownNow(); + } + if (snmpResponseProcessingExecutor != null) { + snmpResponseProcessingExecutor.shutdownNow(); + } + if (snmp != null) { + try { + snmp.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + log.info("SNMP transport stopped!"); + } } From afab5150b8b39aac81a3c7f34a6f6fd438965d3b Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 24 Mar 2021 16:43:28 +0200 Subject: [PATCH 005/127] Provide SNMP transport configuration validation; refactor --- .../data/DeviceTransportConfiguration.java | 3 + .../SnmpDeviceTransportConfiguration.java | 19 +++-- .../DeviceProfileTransportConfiguration.java | 6 +- ...pDeviceProfileTransportConfiguration.java} | 24 +++++-- .../data/transport/snmp/SnmpMapping.java | 39 ++++++++++ .../data/transport/snmp/SnmpMethod.java | 32 +++++++++ .../snmp/SnmpProtocolVersion.java} | 23 +++--- .../transport/snmp/SnmpTransportContext.java | 71 ++++++++----------- .../snmp/service/SnmpTransportService.java | 12 +++- .../snmp/session/DeviceSessionContext.java | 25 ++----- .../dao/device/DeviceProfileServiceImpl.java | 1 + .../server/dao/device/DeviceServiceImpl.java | 4 ++ 12 files changed, 173 insertions(+), 86 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{SnmpProfileTransportConfiguration.java => SnmpDeviceProfileTransportConfiguration.java} (60%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java rename common/data/src/main/java/org/thingsboard/server/common/data/{device/profile/SnmpMapping.java => transport/snmp/SnmpProtocolVersion.java} (68%) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java index 49a547371f..63b31eb3a2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java @@ -38,4 +38,7 @@ public interface DeviceTransportConfiguration extends Serializable { @JsonIgnore DeviceTransportType getType(); + default void validate() { + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index 3db31b4f2a..551bf2fcfb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -19,25 +19,30 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; @Data public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration { - private String address; private int port; private String community; - private String protocolVersion; + private SnmpProtocolVersion protocolVersion; @Override public DeviceTransportType getType() { return DeviceTransportType.SNMP; } + @Override + public void validate() { + if (!isValid()) { + throw new IllegalArgumentException("Transport configuration is not valid"); + } + } + @JsonIgnore - public boolean isValid() { - return StringUtils.isNotEmpty(this.address) - && this.port > 0 - && StringUtils.isNotEmpty(this.community) - && StringUtils.isNotEmpty(this.protocolVersion); + private boolean isValid() { + return StringUtils.isNotBlank(address) && port > 0 && + StringUtils.isNotBlank(community) && protocolVersion != null; } } 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 ffbe5f7048..2fea8d34ad 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 @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.exception.ThingsboardException; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( @@ -30,10 +31,13 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"), - @JsonSubTypes.Type(value = SnmpProfileTransportConfiguration.class, name = "SNMP")}) + @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP")}) public interface DeviceProfileTransportConfiguration { @JsonIgnore DeviceTransportType getType(); + default void validate() { + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java similarity index 60% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index fb6b46fbb6..a4f7cc57de 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import java.util.Collections; import java.util.List; @@ -25,12 +26,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; @Data -public class SnmpProfileTransportConfiguration implements DeviceProfileTransportConfiguration { +public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { private int pollPeriodMs; private int timeoutMs; private int retries; - private List attributesMappings; private List telemetryMappings; + private List attributesMappings; @Override public DeviceTransportType getType() { @@ -39,10 +40,25 @@ public class SnmpProfileTransportConfiguration implements DeviceProfileTransport @JsonIgnore public List getAllMappings() { - if (attributesMappings != null && telemetryMappings != null) { - return Stream.concat(attributesMappings.stream(), telemetryMappings.stream()).collect(Collectors.toList()); + if (telemetryMappings != null && attributesMappings != null) { + return Stream.concat(telemetryMappings.stream(), attributesMappings.stream()).collect(Collectors.toList()); } else { return Collections.emptyList(); } } + + @Override + public void validate() { + if (!isValid()) { + throw new IllegalArgumentException("Transport configuration is not valid"); + } + } + + @JsonIgnore + private boolean isValid() { + List mappings = getAllMappings(); + return pollPeriodMs > 0 && timeoutMs > 0 && retries >= 0 && + !mappings.isEmpty() && mappings.stream().allMatch(SnmpMapping::isValid) && + mappings.stream().map(SnmpMapping::getOid).distinct().count() == mappings.size(); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java new file mode 100644 index 0000000000..32a3f2a25b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2021 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.transport.snmp; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.kv.DataType; + +import java.util.regex.Pattern; + +@Data +public class SnmpMapping { + private String oid; + private SnmpMethod method; + private String key; + private DataType dataType; + + private static final Pattern OID_PATTERN = Pattern.compile("^\\.?([0-2])((\\.0)|(\\.[1-9][0-9]*))*$"); + + @JsonIgnore + public boolean isValid() { + return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && method != null && + StringUtils.isNotBlank(key) && dataType != null; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java new file mode 100644 index 0000000000..2a04ce427b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2021 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.transport.snmp; + +public enum SnmpMethod { + GET(-96), + SET(-93); + + // codes taken from org.snmp4j.PDU class + private final int code; + + SnmpMethod(int code) { + this.code = code; + } + + public int getCode() { + return code; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpProtocolVersion.java similarity index 68% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpProtocolVersion.java index 6bd0ca6b4d..d16836cf1a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpProtocolVersion.java @@ -13,15 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.profile; +package org.thingsboard.server.common.data.transport.snmp; -import lombok.Data; -import org.thingsboard.server.common.data.kv.DataType; +public enum SnmpProtocolVersion { + V1(0), + V2C(1), + V3(3); -@Data -public class SnmpMapping { - private String oid; - private String method; - private String key; - private DataType dataType; + private final int code; + + SnmpProtocolVersion(int code) { + this.code = code; + } + + public int getCode() { + return code; + } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index df2582005a..beaaef829f 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -27,12 +27,13 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.SnmpMapping; -import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; @@ -74,7 +75,7 @@ public class SnmpTransportContext extends TransportContext { private final SnmpTransportBalancingService balancingService; private final Map sessions = new ConcurrentHashMap<>(); - private final Map profilesTransportConfigs = new ConcurrentHashMap<>(); + private final Map profilesTransportConfigs = new ConcurrentHashMap<>(); private final Map> profilesPdus = new ConcurrentHashMap<>(); private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); @@ -94,7 +95,13 @@ public class SnmpTransportContext extends TransportContext { managedDevicesIds.stream() .map(protoEntityService::getDeviceById) .collect(Collectors.toList()) - .forEach(this::establishDeviceSession); + .forEach(device -> { + try { + establishDeviceSession(device); + } catch (Exception e) { + log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.getMessage()); + } + }); } private void establishDeviceSession(Device device) { @@ -110,13 +117,10 @@ public class SnmpTransportContext extends TransportContext { return; } + SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - if (!deviceTransportConfiguration.isValid()) { - log.warn("SNMP device transport configuration is not valid"); - return; - } - SnmpProfileTransportConfiguration profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + profilesTransportConfigs.put(deviceProfileId, profileTransportConfiguration); profilesPdus.computeIfAbsent(deviceProfileId, id -> createPdus(profileTransportConfiguration)); DeviceSessionContext deviceSessionContext = new DeviceSessionContext( @@ -140,22 +144,18 @@ public class SnmpTransportContext extends TransportContext { return; } - SnmpProfileTransportConfiguration profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - sessionContext.setProfileTransportConfiguration(profileTransportConfiguration); - sessionContext.setDeviceTransportConfiguration(deviceTransportConfiguration); - if (!deviceTransportConfiguration.isValid()) { - log.warn("SNMP device transport configuration is not valid"); - destroyDeviceSession(sessionContext); - return; - } + SnmpDeviceProfileTransportConfiguration newProfileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + SnmpDeviceTransportConfiguration newDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); + + if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) { + profilesPdus.put(deviceProfileId, createPdus(newProfileTransportConfiguration)); + profilesTransportConfigs.put(deviceProfileId, newProfileTransportConfiguration); - if (!profileTransportConfiguration.equals(profilesTransportConfigs.get(deviceProfileId))) { - profilesPdus.put(deviceProfileId, createPdus(profileTransportConfiguration)); - profilesTransportConfigs.put(deviceProfileId, profileTransportConfiguration); - sessionContext.initTarget(profileTransportConfiguration, deviceTransportConfiguration); - } else if (!deviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { - sessionContext.initTarget(profileTransportConfiguration, deviceTransportConfiguration); + sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration); + sessionContext.initTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { + sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); + sessionContext.initTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); } else { log.trace("Configuration of the device {} was not updated", device); } @@ -205,8 +205,8 @@ public class SnmpTransportContext extends TransportContext { }); } - private List createPdus(SnmpProfileTransportConfiguration deviceProfileConfig) { - Map> bindingsPerMethod = new HashMap<>(); + private List createPdus(SnmpDeviceProfileTransportConfiguration deviceProfileConfig) { + Map> bindingsPerMethod = new HashMap<>(); deviceProfileConfig.getAllMappings().forEach(mapping -> bindingsPerMethod .computeIfAbsent(mapping.getMethod(), v -> new ArrayList<>()) @@ -215,7 +215,7 @@ public class SnmpTransportContext extends TransportContext { return bindingsPerMethod.keySet().stream() .map(method -> { PDU request = new PDU(); - request.setType(getSnmpMethod(method)); + request.setType(method.getCode()); request.addAll(bindingsPerMethod.get(method)); return request; }) @@ -311,22 +311,9 @@ public class SnmpTransportContext extends TransportContext { return Optional.empty(); } - private Optional getMapping(OID responseOid, List mappings) { + private Optional getMapping(OID oid, List mappings) { return mappings.stream() - .filter(kvMapping -> new OID(kvMapping.getOid()).equals(responseOid)) - //TODO: OID shouldn't be duplicated in the config, add backend and UI verification + .filter(mapping -> new OID(mapping.getOid()).equals(oid)) .findFirst(); } - - private int getSnmpMethod(String configMethod) { - switch (configMethod) { - case "get": - return PDU.GET; - case "getNext": - case "response": - case "set": - default: - return -1; - } - } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 3e9eb63943..86d2cead95 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.event.ResponseEvent; +import org.snmp4j.smi.Null; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; import org.springframework.context.annotation.Lazy; @@ -103,8 +104,8 @@ public class SnmpTransportService implements TbTransportService { try { log.debug("[{}] Sending SNMP message for device {}", pdu.getRequestID(), sessionContext.getDeviceId()); snmp.send(pdu, sessionContext.getTarget(), deviceProfileId, sessionContext); - } catch (IOException e) { - log.error(e.getMessage(), e); + } catch (Exception e) { + log.error("Failed to send SNMP request: {}", e.getMessage()); } }); } @@ -123,7 +124,7 @@ public class SnmpTransportService implements TbTransportService { PDU response = event.getResponse(); if (response == null) { - log.warn("No SNMP response, requestId: {}", event.getRequest().getRequestID()); + log.warn("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); return; } @@ -138,6 +139,11 @@ public class SnmpTransportService implements TbTransportService { VariableBinding variableBinding = response.get(i); log.trace("Processing variable binding {}: {}", i, variableBinding); + if (variableBinding.getVariable() instanceof Null) { + log.debug("Response variable is empty"); + continue; + } + snmpTransportContext.getTelemetryMapping(deviceProfileId, variableBinding.getOid()).ifPresent(mapping -> { log.trace("Found telemetry mapping for oid {}: {}", variableBinding.getOid(), mapping); processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), telemetry); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index ed87bbc726..3cfc9bc041 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -22,13 +22,12 @@ import org.snmp4j.CommunityTarget; import org.snmp4j.Target; import org.snmp4j.event.ResponseEvent; import org.snmp4j.event.ResponseListener; -import org.snmp4j.mp.SnmpConstants; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.OctetString; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; @@ -51,7 +50,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final String token; @Getter @Setter - private SnmpProfileTransportConfiguration profileTransportConfiguration; + private SnmpDeviceProfileTransportConfiguration profileTransportConfiguration; @Getter @Setter private SnmpDeviceTransportConfiguration deviceTransportConfiguration; @@ -79,7 +78,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S this.snmpTransportContext = snmpTransportContext; this.snmpTransportService = snmpTransportService; - this.profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + this.profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); this.deviceTransportConfiguration = deviceTransportConfiguration; initTarget(this.profileTransportConfiguration, this.deviceTransportConfiguration); @@ -105,11 +104,11 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S } } - public void initTarget(SnmpProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + public void initTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { log.trace("Initializing target for SNMP session of device {}", device); CommunityTarget communityTarget = new CommunityTarget(); communityTarget.setAddress(GenericAddress.parse(GenericAddress.TYPE_UDP + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); - communityTarget.setVersion(getSnmpVersion(deviceTransportConfig.getProtocolVersion())); + communityTarget.setVersion(deviceTransportConfig.getProtocolVersion().getCode()); communityTarget.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); communityTarget.setTimeout(profileTransportConfig.getTimeoutMs()); communityTarget.setRetries(profileTransportConfig.getRetries()); @@ -125,20 +124,6 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S return token; } - //TODO: replace with enum, wtih preliminary discussion of type version in config (string or integer) - private int getSnmpVersion(String configSnmpVersion) { - switch (configSnmpVersion) { - case ("v1"): - return SnmpConstants.version1; - case ("v2c"): - return SnmpConstants.version2c; - case ("v3"): - return SnmpConstants.version3; - default: - return -1; - } - } - @Override public int nextMsgId() { return msgIdSeq.incrementAndGet(); 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 95f52cf55e..fea97bc903 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 @@ -350,6 +350,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + transportConfiguration.validate(); if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 64b44db686..f3644d80ac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.device.credentials.BasicMqttCredential import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DeviceData; +import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; @@ -604,6 +605,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe throw new DataValidationException("Can't assign device to customer from different tenant!"); } } + Optional.ofNullable(device.getDeviceData()) + .flatMap(deviceData -> Optional.ofNullable(deviceData.getTransportConfiguration())) + .ifPresent(DeviceTransportConfiguration::validate); } }; From 039508bfbd3d155a5091a130dd4f1701bba9c223 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 24 Mar 2021 12:05:43 +0200 Subject: [PATCH 006/127] Refactor --- ...ProfileKvMapping.java => SnmpMapping.java} | 9 +- .../SnmpProfileTransportConfiguration.java | 10 +- .../DefaultTbServiceInfoProvider.java | 5 +- .../server/queue/util/AfterContextReady.java | 35 +++ .../server/queue/util/AfterStartUp.java | 35 +++ .../transport/snmp/SnmpTransportContext.java | 31 +-- .../snmp/service/SnmpTransportService.java | 237 +++++++----------- 7 files changed, 184 insertions(+), 178 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{SnmpDeviceProfileKvMapping.java => SnmpMapping.java} (90%) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java similarity index 90% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java index 6eff7cbd34..6bd0ca6b4d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileKvMapping.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java @@ -18,11 +18,10 @@ package org.thingsboard.server.common.data.device.profile; import lombok.Data; import org.thingsboard.server.common.data.kv.DataType; -//TODO: rename class @Data -public class SnmpDeviceProfileKvMapping { - private String key; - private DataType type; - private String method; +public class SnmpMapping { private String oid; + private String method; + private String key; + private DataType dataType; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java index 50f6c4351c..fb6b46fbb6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java @@ -29,8 +29,8 @@ public class SnmpProfileTransportConfiguration implements DeviceProfileTransport private int pollPeriodMs; private int timeoutMs; private int retries; - private List attributes; - private List telemetry; + private List attributesMappings; + private List telemetryMappings; @Override public DeviceTransportType getType() { @@ -38,9 +38,9 @@ public class SnmpProfileTransportConfiguration implements DeviceProfileTransport } @JsonIgnore - public List getKvMappings() { - if (attributes != null && telemetry != null) { - return Stream.concat(attributes.stream(), telemetry.stream()).collect(Collectors.toList()); + public List getAllMappings() { + if (attributesMappings != null && telemetryMappings != null) { + return Stream.concat(attributesMappings.stream(), telemetryMappings.stream()).collect(Collectors.toList()); } else { return Collections.emptyList(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java index 3ba360cd8e..6988bef9e1 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/DefaultTbServiceInfoProvider.java @@ -20,8 +20,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationContext; -import org.springframework.context.event.ContextRefreshedEvent; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.TbTransportService; @@ -31,6 +29,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ServiceInfo; import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; +import org.thingsboard.server.queue.util.AfterContextReady; import javax.annotation.PostConstruct; import java.net.InetAddress; @@ -109,7 +108,7 @@ public class DefaultTbServiceInfoProvider implements TbServiceInfoProvider { serviceInfo = builder.build(); } - @EventListener(ContextRefreshedEvent.class) + @AfterContextReady public void setTransports() { serviceInfo = ServiceInfo.newBuilder(serviceInfo) .addAllTransports(getTransportServices().stream() diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java new file mode 100644 index 0000000000..5c68a7609a --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterContextReady.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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.queue.util; + +import org.springframework.context.event.ContextRefreshedEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.Order; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@EventListener(ContextRefreshedEvent.class) +@Order +public @interface AfterContextReady { + @AliasFor(annotation = Order.class, attribute = "value") + int order() default Integer.MAX_VALUE; +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java b/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java new file mode 100644 index 0000000000..5d2e94ee68 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/util/AfterStartUp.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2021 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.queue.util; + +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.AliasFor; +import org.springframework.core.annotation.Order; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +@EventListener(ApplicationReadyEvent.class) +@Order +public @interface AfterStartUp { + @AliasFor(annotation = Order.class, attribute = "value") + int order() default Integer.MAX_VALUE; +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index a9fa8380fa..df2582005a 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -20,16 +20,14 @@ import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.smi.OID; import org.snmp4j.smi.VariableBinding; -import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; -import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; 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.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileKvMapping; +import org.thingsboard.server.common.data.device.profile.SnmpMapping; import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -45,6 +43,7 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.service.ProtoTransportEntityService; import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingService; @@ -61,7 +60,6 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; @TbSnmpTransportComponent @@ -80,8 +78,7 @@ public class SnmpTransportContext extends TransportContext { private final Map> profilesPdus = new ConcurrentHashMap<>(); private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); - @EventListener(ApplicationReadyEvent.class) - @Order(2) + @AfterStartUp(order = 2) public void initDevicesSessions() { log.info("Initializing SNMP devices sessions"); allSnmpDevicesIds = protoEntityService.getAllSnmpDevicesIds().stream() @@ -209,17 +206,17 @@ public class SnmpTransportContext extends TransportContext { } private List createPdus(SnmpProfileTransportConfiguration deviceProfileConfig) { - Map> varBindingPerMethod = new HashMap<>(); + Map> bindingsPerMethod = new HashMap<>(); - deviceProfileConfig.getKvMappings().forEach(mapping -> varBindingPerMethod + deviceProfileConfig.getAllMappings().forEach(mapping -> bindingsPerMethod .computeIfAbsent(mapping.getMethod(), v -> new ArrayList<>()) .add(new VariableBinding(new OID(mapping.getOid())))); - return varBindingPerMethod.keySet().stream() + return bindingsPerMethod.keySet().stream() .map(method -> { PDU request = new PDU(); request.setType(getSnmpMethod(method)); - request.addAll(varBindingPerMethod.get(method)); + request.addAll(bindingsPerMethod.get(method)); return request; }) .collect(Collectors.toList()); @@ -300,31 +297,27 @@ public class SnmpTransportContext extends TransportContext { return profilesPdus; } - public Optional getAttributesMapping(DeviceProfileId deviceProfileId, OID responseOid) { + public Optional getAttributeMapping(DeviceProfileId deviceProfileId, OID responseOid) { if (profilesTransportConfigs.containsKey(deviceProfileId)) { - return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getAttributes()); + return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getAttributesMappings()); } return Optional.empty(); } - public Optional getTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { + public Optional getTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { if (profilesTransportConfigs.containsKey(deviceProfileId)) { - return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getTelemetry()); + return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getTelemetryMappings()); } return Optional.empty(); } - private Optional getMapping(OID responseOid, List mappings) { + private Optional getMapping(OID responseOid, List mappings) { return mappings.stream() .filter(kvMapping -> new OID(kvMapping.getOid()).equals(responseOid)) //TODO: OID shouldn't be duplicated in the config, add backend and UI verification .findFirst(); } - public ExecutorService getSnmpCallbackExecutor() { - return snmpTransportService.getSnmpCallbackExecutor(); - } - private int getSnmpMethod(String configMethod) { switch (configMethod) { case "get": diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 22e60c98ee..3e9eb63943 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -15,114 +15,74 @@ */ package org.thingsboard.server.transport.snmp.service; -import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; -import lombok.AllArgsConstructor; -import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.event.ResponseEvent; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; -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.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.common.transport.auth.SessionInfoCreator; -import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.snmp.SnmpTransportContext; +import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; +import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; -import java.util.UUID; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; @TbSnmpTransportComponent @Service @Slf4j public class SnmpTransportService implements TbTransportService { private final SnmpTransportContext snmpTransportContext; + private final TransportService transportService; - @Getter - private ExecutorService snmpCallbackExecutor; - @Getter private Snmp snmp; private ScheduledExecutorService pollingExecutor; + private ExecutorService snmpResponseProcessingExecutor; - public SnmpTransportService(@Lazy SnmpTransportContext snmpTransportContext) { + public SnmpTransportService(@Lazy SnmpTransportContext snmpTransportContext, + TransportService transportService) { this.snmpTransportContext = snmpTransportContext; + this.transportService = transportService; } - // @PostConstruct - private void init() { - log.info("Starting SNMP transport..."); + @PostConstruct + private void init() throws IOException { + log.info("Initializing SNMP transport service"); + pollingExecutor = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("snmp-polling")); //TODO: Set parallelism value in the config - snmpCallbackExecutor = Executors.newWorkStealingPool(20); + snmpResponseProcessingExecutor = Executors.newWorkStealingPool(20); initializeSnmp(); - log.info("SNMP transport started!"); - } - - @PreDestroy - public void shutdown() { - log.info("Stopping SNMP transport!"); - if (pollingExecutor != null) { - pollingExecutor.shutdownNow(); - } - if (snmpCallbackExecutor != null) { - snmpCallbackExecutor.shutdownNow(); - } - if (snmp != null) { - try { - snmp.close(); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } - log.info("SNMP transport stopped!"); - } - - @EventListener(ApplicationReadyEvent.class) - @Order(value = 10) - public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { - log.info("Received application ready event. Starting SNMP polling."); -// startPolling(); + log.info("SNMP transport service initialized"); } - private void initializeSnmp() { - try { - this.snmp = new Snmp(new DefaultUdpTransportMapping()); - this.snmp.listen(); - } catch (IOException e) { - //TODO: what should be done if transport wasn't initialized? - log.error(e.getMessage(), e); - } + private void initializeSnmp() throws IOException { + snmp = new Snmp(new DefaultUdpTransportMapping()); + snmp.listen(); } - private void startPolling() { + @AfterStartUp(order = 10) + public void startPolling() { + log.info("Starting SNMP polling"); //TODO: Get poll period from configuration; int pollPeriodSeconds = 1; @@ -141,7 +101,7 @@ public class SnmpTransportService implements TbTransportService { DeviceProfileId deviceProfileId = sessionContext.getDeviceProfile().getId(); snmpTransportContext.getProfilesPdus().get(deviceProfileId).forEach(pdu -> { try { - log.debug("[{}] Sending SNMP message...", pdu.getRequestID()); + log.debug("[{}] Sending SNMP message for device {}", pdu.getRequestID(), sessionContext.getDeviceId()); snmp.send(pdu, sessionContext.getTarget(), deviceProfileId, sessionContext); } catch (IOException e) { log.error(e.getMessage(), e); @@ -152,118 +112,84 @@ public class SnmpTransportService implements TbTransportService { public void onNewDeviceResponse(ResponseEvent responseEvent, DeviceSessionContext sessionContext) { ((Snmp) responseEvent.getSource()).cancel(responseEvent.getRequest(), sessionContext); - snmpTransportContext.getSnmpCallbackExecutor().submit(() -> processSnmpResponse(responseEvent, sessionContext)); + snmpResponseProcessingExecutor.submit(() -> processSnmpResponse(responseEvent, sessionContext)); } private void processSnmpResponse(ResponseEvent event, DeviceSessionContext sessionContext) { - PDU response = event.getResponse(); if (event.getError() != null) { log.warn("Response error: {}", event.getError().getMessage(), event.getError()); + return; + } + + PDU response = event.getResponse(); + if (response == null) { + log.warn("No SNMP response, requestId: {}", event.getRequest().getRequestID()); + return; } - if (response != null) { - log.debug("[{}] Processing SNMP response: {}", response.getRequestID(), response); + DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); + log.debug("[{}] Processing SNMP response for device {} with device profile {}: {}", + response.getRequestID(), sessionContext.getDeviceId(), deviceProfileId, response); - DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); - TransportService transportService = snmpTransportContext.getTransportService(); - for (int i = 0; i < response.size(); i++) { - VariableBinding vb = response.get(i); - snmpTransportContext.getAttributesMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, - TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(), - new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { - try { - transportService.process(sessionInfo, - convertToPostAttributes(kvMapping.getKey(), kvMapping.getType(), vb.toValueString()), - TransportServiceCallback.EMPTY); - reportActivity(sessionInfo); - } catch (Exception e) { - log.warn("Failed to process SNMP response: {}", e.getMessage(), e); - } - }))); - snmpTransportContext.getTelemetryMapping(deviceProfileId, vb.getOid()).ifPresent(kvMapping -> transportService.process(DeviceTransportType.DEFAULT, - TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(sessionContext.getToken()).build(), - new DeviceAuthCallback(snmpTransportContext, sessionInfo -> { - try { - transportService.process(sessionInfo, - convertToPostTelemetry(kvMapping.getKey(), kvMapping.getType(), vb.toValueString()), - TransportServiceCallback.EMPTY); - reportActivity(sessionInfo); + JsonObject telemetry = new JsonObject(); + JsonObject attributes = new JsonObject(); - } catch (Exception e) { - log.warn("Failed to process SNMP response: {}", e.getMessage(), e); - } - }))); - } - } else { - log.warn("No SNMP response, requestId: {}", event.getRequest().getRequestID()); + for (int i = 0; i < response.size(); i++) { + VariableBinding variableBinding = response.get(i); + log.trace("Processing variable binding {}: {}", i, variableBinding); + + snmpTransportContext.getTelemetryMapping(deviceProfileId, variableBinding.getOid()).ifPresent(mapping -> { + log.trace("Found telemetry mapping for oid {}: {}", variableBinding.getOid(), mapping); + processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), telemetry); + }); + + snmpTransportContext.getAttributeMapping(deviceProfileId, variableBinding.getOid()).ifPresent(mapping -> { + log.trace("Found attribute mapping for oid {}: {}", variableBinding.getOid(), mapping); + processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), attributes); + }); + } + + if (telemetry.entrySet().isEmpty() && attributes.entrySet().isEmpty()) { + log.warn("No telemetry or attribute values is the SNMP response for device {}", sessionContext.getDeviceId()); + return; } + + if (!telemetry.entrySet().isEmpty()) { + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(telemetry); + transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); + log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), telemetry); + } + + if (!attributes.entrySet().isEmpty()) { + TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(attributes); + transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); + log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), attributes); + } + + reportActivity(sessionContext.getSessionInfo()); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { - snmpTransportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() + transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(false) .setRpcSubscription(false) .setLastActivityTime(System.currentTimeMillis()) .build(), TransportServiceCallback.EMPTY); } - private TransportProtos.PostAttributeMsg convertToPostAttributes(String keyName, DataType dataType, String payload) throws AdaptorException { - try { - return JsonConverter.convertToAttributesProto(getKvJson(keyName, dataType, payload)); - } catch (IllegalStateException | JsonSyntaxException ex) { - //TODO: change the exception type - throw new AdaptorException(ex); - } - } - - private TransportProtos.PostTelemetryMsg convertToPostTelemetry(String keyName, DataType dataType, String payload) throws AdaptorException { - try { - return JsonConverter.convertToTelemetryProto(getKvJson(keyName, dataType, payload)); - } catch (IllegalStateException | JsonSyntaxException ex) { - //TODO: change the exception type - throw new AdaptorException(ex); - } - } - - private JsonElement getKvJson(String keyName, DataType dataType, String payload) throws AdaptorException { - JsonObject result = new JsonObject(); + private void processValue(String key, DataType dataType, String value, JsonObject result) { switch (dataType) { case LONG: - result.addProperty(keyName, Long.parseLong(payload)); + result.addProperty(key, Long.parseLong(value)); break; case BOOLEAN: - result.addProperty(keyName, Boolean.parseBoolean(payload)); + result.addProperty(key, Boolean.parseBoolean(value)); break; case DOUBLE: - result.addProperty(keyName, Double.parseDouble(payload)); - break; - case STRING: - result.addProperty(keyName, payload); + result.addProperty(key, Double.parseDouble(value)); break; default: - //TODO: change the exception type - throw new AdaptorException("Unsupported data type"); - } - return new JsonParser().parse(result.toString()); - } - - @AllArgsConstructor - private static class DeviceAuthCallback implements TransportServiceCallback { - private final TransportContext transportContext; - private final Consumer onSuccess; - - @Override - public void onSuccess(ValidateDeviceCredentialsResponse msg) { - if (msg.hasDeviceInfo()) { - onSuccess.accept(SessionInfoCreator.create(msg, transportContext, UUID.randomUUID())); - } else { - log.warn("Failed to process device auth"); - } - } - - @Override - public void onError(Throwable e) { - log.warn("Failed to process device auth", e); + result.addProperty(key, value); } } @@ -271,4 +197,23 @@ public class SnmpTransportService implements TbTransportService { public String getName() { return "SNMP"; } + + @PreDestroy + public void shutdown() { + log.info("Stopping SNMP transport!"); + if (pollingExecutor != null) { + pollingExecutor.shutdownNow(); + } + if (snmpResponseProcessingExecutor != null) { + snmpResponseProcessingExecutor.shutdownNow(); + } + if (snmp != null) { + try { + snmp.close(); + } catch (IOException e) { + log.error(e.getMessage(), e); + } + } + log.info("SNMP transport stopped!"); + } } From fc06e8ac998ae426af4a21b0fe713b5f9a380285 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 24 Mar 2021 16:43:28 +0200 Subject: [PATCH 007/127] Provide SNMP transport configuration validation; refactor --- .../data/DeviceTransportConfiguration.java | 3 + .../SnmpDeviceTransportConfiguration.java | 19 +++-- .../DeviceProfileTransportConfiguration.java | 6 +- ...pDeviceProfileTransportConfiguration.java} | 24 +++++-- .../data/transport/snmp/SnmpMapping.java | 39 ++++++++++ .../data/transport/snmp/SnmpMethod.java | 32 +++++++++ .../snmp/SnmpProtocolVersion.java} | 23 +++--- .../transport/snmp/SnmpTransportContext.java | 71 ++++++++----------- .../snmp/service/SnmpTransportService.java | 12 +++- .../snmp/session/DeviceSessionContext.java | 25 ++----- .../dao/device/DeviceProfileServiceImpl.java | 1 + .../server/dao/device/DeviceServiceImpl.java | 4 ++ 12 files changed, 173 insertions(+), 86 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/profile/{SnmpProfileTransportConfiguration.java => SnmpDeviceProfileTransportConfiguration.java} (60%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java rename common/data/src/main/java/org/thingsboard/server/common/data/{device/profile/SnmpMapping.java => transport/snmp/SnmpProtocolVersion.java} (68%) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java index 49a547371f..63b31eb3a2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java @@ -38,4 +38,7 @@ public interface DeviceTransportConfiguration extends Serializable { @JsonIgnore DeviceTransportType getType(); + default void validate() { + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index 3db31b4f2a..551bf2fcfb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -19,25 +19,30 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; @Data public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration { - private String address; private int port; private String community; - private String protocolVersion; + private SnmpProtocolVersion protocolVersion; @Override public DeviceTransportType getType() { return DeviceTransportType.SNMP; } + @Override + public void validate() { + if (!isValid()) { + throw new IllegalArgumentException("Transport configuration is not valid"); + } + } + @JsonIgnore - public boolean isValid() { - return StringUtils.isNotEmpty(this.address) - && this.port > 0 - && StringUtils.isNotEmpty(this.community) - && StringUtils.isNotEmpty(this.protocolVersion); + private boolean isValid() { + return StringUtils.isNotBlank(address) && port > 0 && + StringUtils.isNotBlank(community) && protocolVersion != null; } } 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 ffbe5f7048..2fea8d34ad 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 @@ -20,6 +20,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.exception.ThingsboardException; @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( @@ -30,10 +31,13 @@ import org.thingsboard.server.common.data.DeviceTransportType; @JsonSubTypes.Type(value = DefaultDeviceProfileTransportConfiguration.class, name = "DEFAULT"), @JsonSubTypes.Type(value = MqttDeviceProfileTransportConfiguration.class, name = "MQTT"), @JsonSubTypes.Type(value = Lwm2mDeviceProfileTransportConfiguration.class, name = "LWM2M"), - @JsonSubTypes.Type(value = SnmpProfileTransportConfiguration.class, name = "SNMP")}) + @JsonSubTypes.Type(value = SnmpDeviceProfileTransportConfiguration.class, name = "SNMP")}) public interface DeviceProfileTransportConfiguration { @JsonIgnore DeviceTransportType getType(); + default void validate() { + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java similarity index 60% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index fb6b46fbb6..a4f7cc57de 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import java.util.Collections; import java.util.List; @@ -25,12 +26,12 @@ import java.util.stream.Collectors; import java.util.stream.Stream; @Data -public class SnmpProfileTransportConfiguration implements DeviceProfileTransportConfiguration { +public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { private int pollPeriodMs; private int timeoutMs; private int retries; - private List attributesMappings; private List telemetryMappings; + private List attributesMappings; @Override public DeviceTransportType getType() { @@ -39,10 +40,25 @@ public class SnmpProfileTransportConfiguration implements DeviceProfileTransport @JsonIgnore public List getAllMappings() { - if (attributesMappings != null && telemetryMappings != null) { - return Stream.concat(attributesMappings.stream(), telemetryMappings.stream()).collect(Collectors.toList()); + if (telemetryMappings != null && attributesMappings != null) { + return Stream.concat(telemetryMappings.stream(), attributesMappings.stream()).collect(Collectors.toList()); } else { return Collections.emptyList(); } } + + @Override + public void validate() { + if (!isValid()) { + throw new IllegalArgumentException("Transport configuration is not valid"); + } + } + + @JsonIgnore + private boolean isValid() { + List mappings = getAllMappings(); + return pollPeriodMs > 0 && timeoutMs > 0 && retries >= 0 && + !mappings.isEmpty() && mappings.stream().allMatch(SnmpMapping::isValid) && + mappings.stream().map(SnmpMapping::getOid).distinct().count() == mappings.size(); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java new file mode 100644 index 0000000000..32a3f2a25b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2021 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.transport.snmp; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.server.common.data.kv.DataType; + +import java.util.regex.Pattern; + +@Data +public class SnmpMapping { + private String oid; + private SnmpMethod method; + private String key; + private DataType dataType; + + private static final Pattern OID_PATTERN = Pattern.compile("^\\.?([0-2])((\\.0)|(\\.[1-9][0-9]*))*$"); + + @JsonIgnore + public boolean isValid() { + return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && method != null && + StringUtils.isNotBlank(key) && dataType != null; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java new file mode 100644 index 0000000000..2a04ce427b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMethod.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2021 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.transport.snmp; + +public enum SnmpMethod { + GET(-96), + SET(-93); + + // codes taken from org.snmp4j.PDU class + private final int code; + + SnmpMethod(int code) { + this.code = code; + } + + public int getCode() { + return code; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpProtocolVersion.java similarity index 68% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpProtocolVersion.java index 6bd0ca6b4d..d16836cf1a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpMapping.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpProtocolVersion.java @@ -13,15 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.device.profile; +package org.thingsboard.server.common.data.transport.snmp; -import lombok.Data; -import org.thingsboard.server.common.data.kv.DataType; +public enum SnmpProtocolVersion { + V1(0), + V2C(1), + V3(3); -@Data -public class SnmpMapping { - private String oid; - private String method; - private String key; - private DataType dataType; + private final int code; + + SnmpProtocolVersion(int code) { + this.code = code; + } + + public int getCode() { + return code; + } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index df2582005a..beaaef829f 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -27,12 +27,13 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.SnmpMapping; -import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; @@ -74,7 +75,7 @@ public class SnmpTransportContext extends TransportContext { private final SnmpTransportBalancingService balancingService; private final Map sessions = new ConcurrentHashMap<>(); - private final Map profilesTransportConfigs = new ConcurrentHashMap<>(); + private final Map profilesTransportConfigs = new ConcurrentHashMap<>(); private final Map> profilesPdus = new ConcurrentHashMap<>(); private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); @@ -94,7 +95,13 @@ public class SnmpTransportContext extends TransportContext { managedDevicesIds.stream() .map(protoEntityService::getDeviceById) .collect(Collectors.toList()) - .forEach(this::establishDeviceSession); + .forEach(device -> { + try { + establishDeviceSession(device); + } catch (Exception e) { + log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.getMessage()); + } + }); } private void establishDeviceSession(Device device) { @@ -110,13 +117,10 @@ public class SnmpTransportContext extends TransportContext { return; } + SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - if (!deviceTransportConfiguration.isValid()) { - log.warn("SNMP device transport configuration is not valid"); - return; - } - SnmpProfileTransportConfiguration profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + profilesTransportConfigs.put(deviceProfileId, profileTransportConfiguration); profilesPdus.computeIfAbsent(deviceProfileId, id -> createPdus(profileTransportConfiguration)); DeviceSessionContext deviceSessionContext = new DeviceSessionContext( @@ -140,22 +144,18 @@ public class SnmpTransportContext extends TransportContext { return; } - SnmpProfileTransportConfiguration profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); - SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - sessionContext.setProfileTransportConfiguration(profileTransportConfiguration); - sessionContext.setDeviceTransportConfiguration(deviceTransportConfiguration); - if (!deviceTransportConfiguration.isValid()) { - log.warn("SNMP device transport configuration is not valid"); - destroyDeviceSession(sessionContext); - return; - } + SnmpDeviceProfileTransportConfiguration newProfileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + SnmpDeviceTransportConfiguration newDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); + + if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) { + profilesPdus.put(deviceProfileId, createPdus(newProfileTransportConfiguration)); + profilesTransportConfigs.put(deviceProfileId, newProfileTransportConfiguration); - if (!profileTransportConfiguration.equals(profilesTransportConfigs.get(deviceProfileId))) { - profilesPdus.put(deviceProfileId, createPdus(profileTransportConfiguration)); - profilesTransportConfigs.put(deviceProfileId, profileTransportConfiguration); - sessionContext.initTarget(profileTransportConfiguration, deviceTransportConfiguration); - } else if (!deviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { - sessionContext.initTarget(profileTransportConfiguration, deviceTransportConfiguration); + sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration); + sessionContext.initTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { + sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); + sessionContext.initTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); } else { log.trace("Configuration of the device {} was not updated", device); } @@ -205,8 +205,8 @@ public class SnmpTransportContext extends TransportContext { }); } - private List createPdus(SnmpProfileTransportConfiguration deviceProfileConfig) { - Map> bindingsPerMethod = new HashMap<>(); + private List createPdus(SnmpDeviceProfileTransportConfiguration deviceProfileConfig) { + Map> bindingsPerMethod = new HashMap<>(); deviceProfileConfig.getAllMappings().forEach(mapping -> bindingsPerMethod .computeIfAbsent(mapping.getMethod(), v -> new ArrayList<>()) @@ -215,7 +215,7 @@ public class SnmpTransportContext extends TransportContext { return bindingsPerMethod.keySet().stream() .map(method -> { PDU request = new PDU(); - request.setType(getSnmpMethod(method)); + request.setType(method.getCode()); request.addAll(bindingsPerMethod.get(method)); return request; }) @@ -311,22 +311,9 @@ public class SnmpTransportContext extends TransportContext { return Optional.empty(); } - private Optional getMapping(OID responseOid, List mappings) { + private Optional getMapping(OID oid, List mappings) { return mappings.stream() - .filter(kvMapping -> new OID(kvMapping.getOid()).equals(responseOid)) - //TODO: OID shouldn't be duplicated in the config, add backend and UI verification + .filter(mapping -> new OID(mapping.getOid()).equals(oid)) .findFirst(); } - - private int getSnmpMethod(String configMethod) { - switch (configMethod) { - case "get": - return PDU.GET; - case "getNext": - case "response": - case "set": - default: - return -1; - } - } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 3e9eb63943..86d2cead95 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.event.ResponseEvent; +import org.snmp4j.smi.Null; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultUdpTransportMapping; import org.springframework.context.annotation.Lazy; @@ -103,8 +104,8 @@ public class SnmpTransportService implements TbTransportService { try { log.debug("[{}] Sending SNMP message for device {}", pdu.getRequestID(), sessionContext.getDeviceId()); snmp.send(pdu, sessionContext.getTarget(), deviceProfileId, sessionContext); - } catch (IOException e) { - log.error(e.getMessage(), e); + } catch (Exception e) { + log.error("Failed to send SNMP request: {}", e.getMessage()); } }); } @@ -123,7 +124,7 @@ public class SnmpTransportService implements TbTransportService { PDU response = event.getResponse(); if (response == null) { - log.warn("No SNMP response, requestId: {}", event.getRequest().getRequestID()); + log.warn("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); return; } @@ -138,6 +139,11 @@ public class SnmpTransportService implements TbTransportService { VariableBinding variableBinding = response.get(i); log.trace("Processing variable binding {}: {}", i, variableBinding); + if (variableBinding.getVariable() instanceof Null) { + log.debug("Response variable is empty"); + continue; + } + snmpTransportContext.getTelemetryMapping(deviceProfileId, variableBinding.getOid()).ifPresent(mapping -> { log.trace("Found telemetry mapping for oid {}: {}", variableBinding.getOid(), mapping); processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), telemetry); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index ed87bbc726..3cfc9bc041 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -22,13 +22,12 @@ import org.snmp4j.CommunityTarget; import org.snmp4j.Target; import org.snmp4j.event.ResponseEvent; import org.snmp4j.event.ResponseListener; -import org.snmp4j.mp.SnmpConstants; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.OctetString; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.SnmpProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; @@ -51,7 +50,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final String token; @Getter @Setter - private SnmpProfileTransportConfiguration profileTransportConfiguration; + private SnmpDeviceProfileTransportConfiguration profileTransportConfiguration; @Getter @Setter private SnmpDeviceTransportConfiguration deviceTransportConfiguration; @@ -79,7 +78,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S this.snmpTransportContext = snmpTransportContext; this.snmpTransportService = snmpTransportService; - this.profileTransportConfiguration = (SnmpProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + this.profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); this.deviceTransportConfiguration = deviceTransportConfiguration; initTarget(this.profileTransportConfiguration, this.deviceTransportConfiguration); @@ -105,11 +104,11 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S } } - public void initTarget(SnmpProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + public void initTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { log.trace("Initializing target for SNMP session of device {}", device); CommunityTarget communityTarget = new CommunityTarget(); communityTarget.setAddress(GenericAddress.parse(GenericAddress.TYPE_UDP + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); - communityTarget.setVersion(getSnmpVersion(deviceTransportConfig.getProtocolVersion())); + communityTarget.setVersion(deviceTransportConfig.getProtocolVersion().getCode()); communityTarget.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); communityTarget.setTimeout(profileTransportConfig.getTimeoutMs()); communityTarget.setRetries(profileTransportConfig.getRetries()); @@ -125,20 +124,6 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S return token; } - //TODO: replace with enum, wtih preliminary discussion of type version in config (string or integer) - private int getSnmpVersion(String configSnmpVersion) { - switch (configSnmpVersion) { - case ("v1"): - return SnmpConstants.version1; - case ("v2c"): - return SnmpConstants.version2c; - case ("v3"): - return SnmpConstants.version3; - default: - return -1; - } - } - @Override public int nextMsgId() { return msgIdSeq.incrementAndGet(); 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 95f52cf55e..fea97bc903 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 @@ -350,6 +350,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + transportConfiguration.validate(); if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 64b44db686..f3644d80ac 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.device.credentials.BasicMqttCredential import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DeviceData; +import org.thingsboard.server.common.data.device.data.DeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; @@ -604,6 +605,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe throw new DataValidationException("Can't assign device to customer from different tenant!"); } } + Optional.ofNullable(device.getDeviceData()) + .flatMap(deviceData -> Optional.ofNullable(deviceData.getTransportConfiguration())) + .ifPresent(DeviceTransportConfiguration::validate); } }; From 306ace040cb94fdd777e392af98f772f31bb2ee5 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 25 Mar 2021 10:06:06 +0200 Subject: [PATCH 008/127] Refactor --- .../src/main/resources/thingsboard.yml | 5 + .../JsonBasedTransportConfiguration.java | 39 ---- common/transport/snmp/pom.xml | 6 + .../transport/snmp/SnmpDeviceSimulator.java | 81 --------- .../transport/snmp/SnmpTransportContext.java | 10 +- .../snmp/service/SnmpTransportService.java | 34 +++- .../snmp/session/DeviceSessionContext.java | 13 +- .../transport/snmp/SnmpDeviceSimulator.java | 168 ++++++++++++++++++ .../snmp-device-profile-transport-config.json | 27 +++ .../snmp-device-transport-config.json | 6 + .../main/resources/device-data-config.json | 9 - .../main/resources/device-profile-config.json | 21 --- .../src/main/resources/tb-snmp-transport.yml | 5 + 13 files changed, 257 insertions(+), 167 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/JsonBasedTransportConfiguration.java delete mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java create mode 100644 common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java create mode 100644 common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json create mode 100644 common/transport/snmp/src/test/resources/snmp-device-transport-config.json delete mode 100644 transport/snmp/src/main/resources/device-data-config.json delete mode 100644 transport/snmp/src/main/resources/device-profile-config.json diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 3f7ef449ed..2cedd30bc8 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -647,6 +647,11 @@ transport: redis.enabled: "${LWM2M_REDIS_ENABLED:false}" snmp: enabled: "${SNMP_ENABLED:true}" + response_processing: + # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices + parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" + # to configure SNMP to work over UDP or TCP + underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" swagger: api_path_regex: "${SWAGGER_API_PATH_REGEX:/api.*}" diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/JsonBasedTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/JsonBasedTransportConfiguration.java deleted file mode 100644 index c65e9dd972..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/JsonBasedTransportConfiguration.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright © 2016-2021 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; - -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; - -import java.util.HashMap; -import java.util.Map; - -public abstract class JsonBasedTransportConfiguration { - - @JsonIgnore - private final Map properties = new HashMap<>(); - - @JsonAnyGetter - public Map properties() { - return this.properties; - } - - @JsonAnySetter - public void put(String name, Object value) { - this.properties.put(name, value); - } -} diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index ab83ab69a7..afa35b518c 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -58,5 +58,11 @@ org.snmp4j snmp4j + + org.snmp4j + snmp4j-agent + 3.3.6 + test + diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java deleted file mode 100644 index 7109ef4e3f..0000000000 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright © 2016-2021 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.snmp; - -import org.snmp4j.CommunityTarget; -import org.snmp4j.PDU; -import org.snmp4j.Snmp; -import org.snmp4j.Target; -import org.snmp4j.event.ResponseEvent; -import org.snmp4j.mp.SnmpConstants; -import org.snmp4j.smi.GenericAddress; -import org.snmp4j.smi.OID; -import org.snmp4j.smi.OctetString; -import org.snmp4j.smi.VariableBinding; -import org.snmp4j.transport.DefaultUdpTransportMapping; -import org.snmp4j.transport.UdpTransportMapping; - -import java.io.IOException; - -/** - * For testing purposes. Will be removed when the time comes - */ -public class SnmpDeviceSimulator { - private final Target target; - private final OID oid = new OID(".1.3.6.1.2.1.1.1.0"); - private Snmp snmp; - - public SnmpDeviceSimulator(int port) { - String address = "udp:127.0.0.1/" + port; - - CommunityTarget target = new CommunityTarget(); - target.setCommunity(new OctetString("public")); - target.setAddress(GenericAddress.parse(address)); - target.setRetries(2); - target.setTimeout(1500); - target.setVersion(SnmpConstants.version2c); - - this.target = target; - } - - public static void main(String[] args) throws IOException { - SnmpDeviceSimulator deviceSimulator = new SnmpDeviceSimulator(161); - - deviceSimulator.start(); - String response = deviceSimulator.sendRequest(PDU.GET); - - System.out.println(response); - } - - public void start() throws IOException { - UdpTransportMapping transport = new DefaultUdpTransportMapping(); - transport.addTransportListener((sourceTransport, incomingAddress, wholeMessage, tmStateReference) -> { - System.out.println(); - }); - snmp = new Snmp(transport); - - transport.listen(); - } - - public String sendRequest(int pduType) throws IOException { - PDU pdu = new PDU(); - pdu.add(new VariableBinding(oid)); - pdu.setType(pduType); - - ResponseEvent responseEvent = snmp.send(pdu, target); - return responseEvent.getResponse().get(0).getVariable().toString(); - } -} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index beaaef829f..2c1896a505 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.smi.OID; import org.snmp4j.smi.VariableBinding; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Device; @@ -79,6 +80,9 @@ public class SnmpTransportContext extends TransportContext { private final Map> profilesPdus = new ConcurrentHashMap<>(); private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); + @Value("${transport.snmp.underlying_protocol}") + private String snmpUnderlyingProtocol; + @AfterStartUp(order = 2) public void initDevicesSessions() { log.info("Initializing SNMP devices sessions"); @@ -124,9 +128,9 @@ public class SnmpTransportContext extends TransportContext { profilesPdus.computeIfAbsent(deviceProfileId, id -> createPdus(profileTransportConfiguration)); DeviceSessionContext deviceSessionContext = new DeviceSessionContext( - device, deviceProfile, - credentials.getCredentialsId(), deviceTransportConfiguration, - this, snmpTransportService + device, deviceProfile, credentials.getCredentialsId(), + deviceTransportConfiguration, this, + snmpTransportService, snmpUnderlyingProtocol ); registerSessionMsgListener(deviceSessionContext); sessions.put(device.getId(), deviceSessionContext); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 86d2cead95..fd386e612a 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -19,10 +19,13 @@ import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import org.snmp4j.PDU; import org.snmp4j.Snmp; +import org.snmp4j.TransportMapping; import org.snmp4j.event.ResponseEvent; import org.snmp4j.smi.Null; import org.snmp4j.smi.VariableBinding; +import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -57,6 +60,11 @@ public class SnmpTransportService implements TbTransportService { private ScheduledExecutorService pollingExecutor; private ExecutorService snmpResponseProcessingExecutor; + @Value("${transport.snmp.response_processing.parallelism_level}") + private Integer responseProcessingParallelismLevel; + @Value("${transport.snmp.underlying_protocol}") + private String snmpUnderlyingProtocol; + public SnmpTransportService(@Lazy SnmpTransportContext snmpTransportContext, TransportService transportService) { this.snmpTransportContext = snmpTransportContext; @@ -68,8 +76,7 @@ public class SnmpTransportService implements TbTransportService { log.info("Initializing SNMP transport service"); pollingExecutor = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("snmp-polling")); - //TODO: Set parallelism value in the config - snmpResponseProcessingExecutor = Executors.newWorkStealingPool(20); + snmpResponseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); initializeSnmp(); @@ -77,19 +84,28 @@ public class SnmpTransportService implements TbTransportService { } private void initializeSnmp() throws IOException { - snmp = new Snmp(new DefaultUdpTransportMapping()); + TransportMapping transportMapping; + switch (snmpUnderlyingProtocol) { + case "udp": + transportMapping = new DefaultUdpTransportMapping(); + break; + case "tcp": + transportMapping = new DefaultTcpTransportMapping(); + break; + default: + throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported"); + } + snmp = new Snmp(transportMapping); snmp.listen(); } @AfterStartUp(order = 10) public void startPolling() { log.info("Starting SNMP polling"); - //TODO: Get poll period from configuration; - int pollPeriodSeconds = 1; pollingExecutor.scheduleWithFixedDelay(() -> { snmpTransportContext.getSessions().forEach(this::executeSnmpRequest); - }, 0, pollPeriodSeconds, TimeUnit.SECONDS); + }, 0, 1, TimeUnit.SECONDS); } private void executeSnmpRequest(DeviceSessionContext sessionContext) { @@ -111,12 +127,12 @@ public class SnmpTransportService implements TbTransportService { } } - public void onNewDeviceResponse(ResponseEvent responseEvent, DeviceSessionContext sessionContext) { + public void onNewDeviceResponse(DeviceSessionContext sessionContext, ResponseEvent responseEvent) { ((Snmp) responseEvent.getSource()).cancel(responseEvent.getRequest(), sessionContext); - snmpResponseProcessingExecutor.submit(() -> processSnmpResponse(responseEvent, sessionContext)); + snmpResponseProcessingExecutor.submit(() -> processSnmpResponse(sessionContext, responseEvent)); } - private void processSnmpResponse(ResponseEvent event, DeviceSessionContext sessionContext) { + private void processSnmpResponse(DeviceSessionContext sessionContext, ResponseEvent event) { if (event.getError() != null) { log.warn("Response error: {}", event.getError().getMessage(), event.getError()); return; diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index 3cfc9bc041..57e89ad034 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -65,10 +65,12 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private long previousRequestExecutedAt = 0; private final AtomicInteger msgIdSeq = new AtomicInteger(0); private boolean isActive = true; + private final String snmpUnderlyingProtocol; - public DeviceSessionContext(Device device, DeviceProfile deviceProfile, - String token, SnmpDeviceTransportConfiguration deviceTransportConfiguration, - SnmpTransportContext snmpTransportContext, SnmpTransportService snmpTransportService) { + public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, + SnmpDeviceTransportConfiguration deviceTransportConfiguration, + SnmpTransportContext snmpTransportContext, SnmpTransportService snmpTransportService, + String snmpUnderlyingProtocol) { super(UUID.randomUUID()); super.setDeviceId(device.getId()); super.setDeviceProfile(deviceProfile); @@ -81,6 +83,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S this.profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); this.deviceTransportConfiguration = deviceTransportConfiguration; + this.snmpUnderlyingProtocol = snmpUnderlyingProtocol; initTarget(this.profileTransportConfiguration, this.deviceTransportConfiguration); } @@ -100,14 +103,14 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onResponse(ResponseEvent event) { if (isActive) { - snmpTransportService.onNewDeviceResponse(event, this); + snmpTransportService.onNewDeviceResponse(this, event); } } public void initTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { log.trace("Initializing target for SNMP session of device {}", device); CommunityTarget communityTarget = new CommunityTarget(); - communityTarget.setAddress(GenericAddress.parse(GenericAddress.TYPE_UDP + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); + communityTarget.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); communityTarget.setVersion(deviceTransportConfig.getProtocolVersion().getCode()); communityTarget.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); communityTarget.setTimeout(profileTransportConfig.getTimeoutMs()); diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java new file mode 100644 index 0000000000..29fa35dcab --- /dev/null +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java @@ -0,0 +1,168 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import org.snmp4j.CommunityTarget; +import org.snmp4j.Target; +import org.snmp4j.TransportMapping; +import org.snmp4j.agent.BaseAgent; +import org.snmp4j.agent.CommandProcessor; +import org.snmp4j.agent.DuplicateRegistrationException; +import org.snmp4j.agent.MOGroup; +import org.snmp4j.agent.ManagedObject; +import org.snmp4j.agent.mo.MOAccessImpl; +import org.snmp4j.agent.mo.MOScalar; +import org.snmp4j.agent.mo.snmp.RowStatus; +import org.snmp4j.agent.mo.snmp.SnmpCommunityMIB; +import org.snmp4j.agent.mo.snmp.SnmpNotificationMIB; +import org.snmp4j.agent.mo.snmp.SnmpTargetMIB; +import org.snmp4j.agent.mo.snmp.StorageType; +import org.snmp4j.agent.mo.snmp.VacmMIB; +import org.snmp4j.agent.security.MutableVACM; +import org.snmp4j.mp.MPv3; +import org.snmp4j.mp.SnmpConstants; +import org.snmp4j.security.SecurityLevel; +import org.snmp4j.security.SecurityModel; +import org.snmp4j.security.USM; +import org.snmp4j.smi.Address; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.Integer32; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.Variable; +import org.snmp4j.transport.TransportMappings; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.Scanner; + +public class SnmpDeviceSimulator extends BaseAgent { + + public static void main(String[] args) throws IOException { + SnmpDeviceSimulator device = new SnmpDeviceSimulator(1610); + + device.start(); + device.setUpMappings(Map.of( + ".1.3.6.1.2.1.1.1.50", "12", + ".1.3.6.1.2.1.2.1.52", "56", + ".1.3.6.1.2.1.3.1.54", "yes" + )); + + new Scanner(System.in).nextLine(); + } + + + private final Target target; + private final Address address; + + public SnmpDeviceSimulator(int port) { + super(new File("conf.agent"), new File("bootCounter.agent"), + new CommandProcessor(new OctetString(MPv3.createLocalEngineID()))); + + CommunityTarget target = new CommunityTarget(); + target.setCommunity(new OctetString("public")); + address = GenericAddress.parse("udp:0.0.0.0/" + port); + target.setAddress(address); + target.setRetries(2); + target.setTimeout(1500); + target.setVersion(SnmpConstants.version2c); + this.target = target; + } + + public void start() throws IOException { + init(); + addShutdownHook(); + getServer().addContext(new OctetString("public")); + finishInit(); + run(); + sendColdStartNotification(); + } + + public void setUpMappings(Map oidToResponseMappings) { + unregisterManagedObject(getSnmpv2MIB()); + oidToResponseMappings.forEach((oid, response) -> { + registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_ONLY, new OctetString(response))); + }); + } + + public Target getTarget() { + return target; + } + + @Override + protected void registerManagedObjects() { + } + + protected void registerManagedObject(ManagedObject mo) { + try { + server.register(mo, null); + } catch (DuplicateRegistrationException ex) { + throw new RuntimeException(ex); + } + } + + protected void unregisterManagedObject(MOGroup moGroup) { + moGroup.unregisterMOs(server, getContext(moGroup)); + } + + @Override + protected void addNotificationTargets(SnmpTargetMIB targetMIB, + SnmpNotificationMIB notificationMIB) { + } + + @Override + protected void addViews(VacmMIB vacm) { + vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c, new OctetString( + "cpublic"), new OctetString("v1v2group"), + StorageType.nonVolatile); + + vacm.addAccess(new OctetString("v1v2group"), new OctetString("public"), + SecurityModel.SECURITY_MODEL_ANY, SecurityLevel.NOAUTH_NOPRIV, + MutableVACM.VACM_MATCH_EXACT, new OctetString("fullReadView"), + new OctetString("fullWriteView"), new OctetString( + "fullNotifyView"), StorageType.nonVolatile); + + vacm.addViewTreeFamily(new OctetString("fullReadView"), new OID("1.3"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + } + + protected void addUsmUser(USM usm) { + } + + protected void initTransportMappings() { + transportMappings = new TransportMapping[]{TransportMappings.getInstance().createTransportMapping(address)}; + } + + protected void unregisterManagedObjects() { + } + + protected void addCommunities(SnmpCommunityMIB communityMIB) { + Variable[] com2sec = new Variable[]{ + new OctetString("public"), + new OctetString("cpublic"), + getAgent().getContextEngineID(), + new OctetString("public"), + new OctetString(), + new Integer32(StorageType.nonVolatile), + new Integer32(RowStatus.active) + }; + SnmpCommunityMIB.SnmpCommunityEntryRow row = communityMIB.getSnmpCommunityEntry().createRow( + new OctetString("public2public").toSubIndex(true), com2sec); + communityMIB.getSnmpCommunityEntry().addRow(row); + } +} diff --git a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json new file mode 100644 index 0000000000..78b66c4c0e --- /dev/null +++ b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json @@ -0,0 +1,27 @@ +{ + "pollPeriodMs": 3000, + "timeoutMs": 500, + "retries": 0, + "telemetryMappings": [ + { + "oid": ".1.3.6.1.2.1.1.1.50", + "method": "GET", + "key": "temperature", + "dataType": "LONG" + }, + { + "oid": ".1.3.6.1.2.1.2.1.52", + "method": "GET", + "key": "humidity", + "dataType": "LONG" + } + ], + "attributesMappings": [ + { + "oid": ".1.3.6.1.2.1.3.1.54", + "method": "GET", + "key": "isCool", + "dataType": "STRING" + } + ] +} \ No newline at end of file diff --git a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json new file mode 100644 index 0000000000..c73d817bfb --- /dev/null +++ b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json @@ -0,0 +1,6 @@ +{ + "address": "127.0.0.1", + "port": 1610, + "community": "public", + "protocolVersion": "V2C" +} \ No newline at end of file diff --git a/transport/snmp/src/main/resources/device-data-config.json b/transport/snmp/src/main/resources/device-data-config.json deleted file mode 100644 index e425ccc3a6..0000000000 --- a/transport/snmp/src/main/resources/device-data-config.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "deviceName": "Thermostat T1", - "snmpConfig": { - "address": "192.168.1.2", - "port": 161, - "community": "U5J=$HWj6f@7", - "protocolVersion": "v2c" - } -} diff --git a/transport/snmp/src/main/resources/device-profile-config.json b/transport/snmp/src/main/resources/device-profile-config.json deleted file mode 100644 index b272913045..0000000000 --- a/transport/snmp/src/main/resources/device-profile-config.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "poolPeriodMs": 10000, - "timeoutMs": 5000, - "retries": 5, - "attributes": [ - { - "key": "snmpNodeManagerEmail", - "type": "STRING", - "method": "get", - "oid": ".1.3.6.1.2.1.1.4.0" - } - ], - "telemetry": [ - { - "key": "snmpNodeSysUpTime", - "type": "LONG", - "method": "get", - "oid": ".1.3.6.1.2.1.1.3.0" - } - ] -} \ No newline at end of file diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index 9f6bd131ac..db9f79b51a 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -189,6 +189,11 @@ transport: # Local LwM2M transport parameters snmp: enabled: "${SNMP_ENABLED:true}" + response_processing: + # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices + parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" + # to configure SNMP to work over UDP or TCP + underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" queue: type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) From 61341a7cafdf9cbbe59fc8b31bc7827b1ad19382 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 1 Apr 2021 15:50:37 +0300 Subject: [PATCH 009/127] Refactor, implement SNMP traps, add support for SNMP v3 targets --- .../SnmpDeviceTransportConfiguration.java | 6 +- ...mpDeviceProfileTransportConfiguration.java | 23 +- .../transport/snmp/SnmpCommunicationSpec.java | 40 +++ .../data/transport/snmp/SnmpMapping.java | 3 +- ...ibutesQueryingSnmpCommunicationConfig.java | 25 ++ ...TrapsReceivingSnmpCommunicationConfig.java | 25 ++ ...eatingQueryingSnmpCommunicationConfig.java | 36 +++ ...ributesSettingSnmpCommunicationConfig.java | 36 +++ .../snmp/configs/SnmpCommunicationConfig.java | 62 +++++ ...emetryQueryingSnmpCommunicationConfig.java | 29 ++ ...TrapsReceivingSnmpCommunicationConfig.java | 30 ++ .../transport/snmp/SnmpTransportContext.java | 83 +----- .../snmp/service/SnmpTransportService.java | 263 ++++++++++++++---- .../snmp/session/DeviceSessionContext.java | 105 ++++++- ...ulator.java => SnmpDeviceSimulatorV2.java} | 76 ++++- .../transport/snmp/SnmpDeviceSimulatorV3.java | 52 ++++ .../server/transport/snmp/SnmpTest.java | 49 ++++ .../snmp-device-profile-transport-config.json | 62 +++-- .../snmp-device-transport-config.json | 2 +- .../transport/adaptor/JsonConverter.java | 2 +- 20 files changed, 810 insertions(+), 199 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java rename common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/{SnmpDeviceSimulator.java => SnmpDeviceSimulatorV2.java} (69%) create mode 100644 common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java create mode 100644 common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index 551bf2fcfb..a6056b4188 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -25,8 +25,10 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration { private String address; private int port; - private String community; private SnmpProtocolVersion protocolVersion; + private String securityName; + private String authenticationPassphrase; // for SNMP v3 + private String privacyPassphrase; // for SNMP v3 @Override public DeviceTransportType getType() { @@ -43,6 +45,6 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur @JsonIgnore private boolean isValid() { return StringUtils.isNotBlank(address) && port > 0 && - StringUtils.isNotBlank(community) && protocolVersion != null; + StringUtils.isNotBlank(securityName) && protocolVersion != null; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index a4f7cc57de..843a51304e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -18,35 +18,21 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Data public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - private int pollPeriodMs; private int timeoutMs; private int retries; - private List telemetryMappings; - private List attributesMappings; + private List communicationConfigs; @Override public DeviceTransportType getType() { return DeviceTransportType.SNMP; } - @JsonIgnore - public List getAllMappings() { - if (telemetryMappings != null && attributesMappings != null) { - return Stream.concat(telemetryMappings.stream(), attributesMappings.stream()).collect(Collectors.toList()); - } else { - return Collections.emptyList(); - } - } - @Override public void validate() { if (!isValid()) { @@ -56,9 +42,6 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra @JsonIgnore private boolean isValid() { - List mappings = getAllMappings(); - return pollPeriodMs > 0 && timeoutMs > 0 && retries >= 0 && - !mappings.isEmpty() && mappings.stream().allMatch(SnmpMapping::isValid) && - mappings.stream().map(SnmpMapping::getOid).distinct().count() == mappings.size(); + return true; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java new file mode 100644 index 0000000000..f6ff7d3109 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2021 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.transport.snmp; + +public enum SnmpCommunicationSpec { + TELEMETRY_QUERYING(true), + CLIENT_ATTRIBUTES_QUERYING(true), + + SHARED_ATTRIBUTES_SETTING, + + TELEMETRY_TRAPS_RECEIVING, + CLIENT_ATTRIBUTES_TRAPS_RECEIVING; + + private final boolean isRepeatingQuerying; + + SnmpCommunicationSpec() { + this.isRepeatingQuerying = false; + } + + SnmpCommunicationSpec(boolean isRepeatingQuerying) { + this.isRepeatingQuerying = isRepeatingQuerying; + } + + public boolean isRepeatingQuerying() { + return isRepeatingQuerying; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java index 32a3f2a25b..0269491461 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java @@ -25,7 +25,6 @@ import java.util.regex.Pattern; @Data public class SnmpMapping { private String oid; - private SnmpMethod method; private String key; private DataType dataType; @@ -33,7 +32,7 @@ public class SnmpMapping { @JsonIgnore public boolean isValid() { - return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && method != null && + return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && StringUtils.isNotBlank(key) && dataType != null; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..16198f8ef6 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.configs; + +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; + +public class ClientAttributesQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..9a11b01f1b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.configs; + +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; + +public class ClientAttributesTrapsReceivingSnmpCommunicationConfig extends SnmpCommunicationConfig { + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.CLIENT_ATTRIBUTES_TRAPS_RECEIVING; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..764bf97b15 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.configs; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; + +@EqualsAndHashCode(callSuper = true) +@Data +public abstract class RepeatingQueryingSnmpCommunicationConfig extends SnmpCommunicationConfig { + private Long queryingFrequencyMs; + + @Override + public SnmpMethod getMethod() { + return SnmpMethod.GET; + } + + @Override + public boolean isValid() { + return true; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..1c63dae618 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.configs; + +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; + +public class SharedAttributesSettingSnmpCommunicationConfig extends SnmpCommunicationConfig { + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING; + } + + @Override + public SnmpMethod getMethod() { + return SnmpMethod.SET; + } + + @Override + public boolean isValid() { + return true; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java new file mode 100644 index 0000000000..bfc194669b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java @@ -0,0 +1,62 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.configs; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "spec") +@JsonSubTypes({ + @Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"), + @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), + @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"), + @Type(value = TelemetryTrapsReceivingSnmpCommunicationConfig.class, name = "TELEMETRY_TRAPS_RECEIVING"), + @Type(value = ClientAttributesTrapsReceivingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_TRAPS_RECEIVING") +}) +public abstract class SnmpCommunicationConfig { + protected List mappings; + + public List getMappings() { + return mappings; + } + + public abstract SnmpCommunicationSpec getSpec(); + + @JsonIgnore + public SnmpMethod getMethod() { + return null; + } + + @JsonIgnore + public boolean isValid() { + return true; + } + + public void validate() { + if (!isValid()) { + throw new IllegalArgumentException("Communication config is not valid"); + } + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..68d3c81ed2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.configs; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; + +@EqualsAndHashCode(callSuper = true) +@Data +public class TelemetryQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.TELEMETRY_QUERYING; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..66be4d0cf8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.configs; + +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; + +public class TelemetryTrapsReceivingSnmpCommunicationConfig extends SnmpCommunicationConfig { + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.TELEMETRY_TRAPS_RECEIVING; + } + + @Override + public boolean isValid() { + return false; + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 2c1896a505..877d4f1652 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -17,9 +17,6 @@ package org.thingsboard.server.transport.snmp; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.snmp4j.PDU; -import org.snmp4j.smi.OID; -import org.snmp4j.smi.VariableBinding; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @@ -33,8 +30,6 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; @@ -42,7 +37,6 @@ import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.queue.util.AfterStartUp; @@ -52,9 +46,7 @@ import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingServi import org.thingsboard.server.transport.snmp.service.SnmpTransportService; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; -import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -76,8 +68,6 @@ public class SnmpTransportContext extends TransportContext { private final SnmpTransportBalancingService balancingService; private final Map sessions = new ConcurrentHashMap<>(); - private final Map profilesTransportConfigs = new ConcurrentHashMap<>(); - private final Map> profilesPdus = new ConcurrentHashMap<>(); private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); @Value("${transport.snmp.underlying_protocol}") @@ -124,22 +114,19 @@ public class SnmpTransportContext extends TransportContext { SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - profilesTransportConfigs.put(deviceProfileId, profileTransportConfiguration); - profilesPdus.computeIfAbsent(deviceProfileId, id -> createPdus(profileTransportConfiguration)); - DeviceSessionContext deviceSessionContext = new DeviceSessionContext( device, deviceProfile, credentials.getCredentialsId(), - deviceTransportConfiguration, this, - snmpTransportService, snmpUnderlyingProtocol + profileTransportConfiguration, deviceTransportConfiguration, + this, snmpTransportService, snmpUnderlyingProtocol ); registerSessionMsgListener(deviceSessionContext); sessions.put(device.getId(), deviceSessionContext); + snmpTransportService.createQueryingTasks(deviceSessionContext); log.info("Established SNMP device session for device {}", device.getId()); } private void updateDeviceSession(DeviceSessionContext sessionContext, Device device, DeviceProfile deviceProfile) { log.info("Updating SNMP device session for device {}", device.getId()); - DeviceProfileId deviceProfileId = deviceProfile.getId(); DeviceCredentials credentials = protoEntityService.getDeviceCredentialsByDeviceId(device.getId()); if (credentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { @@ -152,14 +139,13 @@ public class SnmpTransportContext extends TransportContext { SnmpDeviceTransportConfiguration newDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) { - profilesPdus.put(deviceProfileId, createPdus(newProfileTransportConfiguration)); - profilesTransportConfigs.put(deviceProfileId, newProfileTransportConfiguration); - sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration); - sessionContext.initTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + snmpTransportService.cancelQueryingTasks(sessionContext); + snmpTransportService.createQueryingTasks(sessionContext); } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); - sessionContext.initTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); } else { log.trace("Configuration of the device {} was not updated", device); } @@ -171,22 +157,14 @@ public class SnmpTransportContext extends TransportContext { sessionContext.close(); transportService.deregisterSession(sessionContext.getSessionInfo()); sessions.remove(sessionContext.getDeviceId()); - log.trace("Deregistered and removed session"); - - DeviceProfileId deviceProfileId = sessionContext.getDeviceProfile().getId(); - if (sessions.values().stream() - .map(DeviceAwareSessionContext::getDeviceProfile) - .noneMatch(deviceProfile -> deviceProfile.getId().equals(deviceProfileId))) { - log.trace("Removed values for device profile {} from configs and pdus caches", deviceProfileId); - profilesTransportConfigs.remove(deviceProfileId); - profilesPdus.remove(deviceProfileId); - } + snmpTransportService.cancelQueryingTasks(sessionContext); + log.trace("Unregistered and removed session"); } private void registerSessionMsgListener(DeviceSessionContext deviceSessionContext) { transportService.process(DeviceTransportType.SNMP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceSessionContext.getToken()).build(), - new TransportServiceCallback() { + new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { if (msg.hasDeviceInfo()) { @@ -195,6 +173,7 @@ public class SnmpTransportContext extends TransportContext { ); transportService.registerAsyncSession(sessionInfo, deviceSessionContext); + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); deviceSessionContext.setSessionInfo(sessionInfo); deviceSessionContext.setDeviceInfo(msg.getDeviceInfo()); } else { @@ -209,23 +188,6 @@ public class SnmpTransportContext extends TransportContext { }); } - private List createPdus(SnmpDeviceProfileTransportConfiguration deviceProfileConfig) { - Map> bindingsPerMethod = new HashMap<>(); - - deviceProfileConfig.getAllMappings().forEach(mapping -> bindingsPerMethod - .computeIfAbsent(mapping.getMethod(), v -> new ArrayList<>()) - .add(new VariableBinding(new OID(mapping.getOid())))); - - return bindingsPerMethod.keySet().stream() - .map(method -> { - PDU request = new PDU(); - request.setType(method.getCode()); - request.addAll(bindingsPerMethod.get(method)); - return request; - }) - .collect(Collectors.toList()); - } - @EventListener(DeviceUpdatedEvent.class) public void onDeviceUpdatedOrCreated(DeviceUpdatedEvent deviceUpdatedEvent) { Device device = deviceUpdatedEvent.getDevice(); @@ -297,27 +259,4 @@ public class SnmpTransportContext extends TransportContext { return sessions.values(); } - public Map> getProfilesPdus() { - return profilesPdus; - } - - public Optional getAttributeMapping(DeviceProfileId deviceProfileId, OID responseOid) { - if (profilesTransportConfigs.containsKey(deviceProfileId)) { - return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getAttributesMappings()); - } - return Optional.empty(); - } - - public Optional getTelemetryMapping(DeviceProfileId deviceProfileId, OID responseOid) { - if (profilesTransportConfigs.containsKey(deviceProfileId)) { - return getMapping(responseOid, profilesTransportConfigs.get(deviceProfileId).getTelemetryMappings()); - } - return Optional.empty(); - } - - private Optional getMapping(OID oid, List mappings) { - return mappings.stream() - .filter(mapping -> new OID(mapping.getOid()).equals(oid)) - .findFirst(); - } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index fd386e612a..963b5db382 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -16,12 +16,20 @@ package org.thingsboard.server.transport.snmp.service; import com.google.gson.JsonObject; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.snmp4j.CommandResponder; +import org.snmp4j.CommandResponderEvent; import org.snmp4j.PDU; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; import org.snmp4j.event.ResponseEvent; import org.snmp4j.smi.Null; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.TcpAddress; +import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; @@ -32,11 +40,14 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.configs.RepeatingQueryingSnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; @@ -44,21 +55,33 @@ import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; @TbSnmpTransportComponent @Service @Slf4j -public class SnmpTransportService implements TbTransportService { +public class SnmpTransportService implements TbTransportService, CommandResponder { private final SnmpTransportContext snmpTransportContext; private final TransportService transportService; + @Getter private Snmp snmp; - private ScheduledExecutorService pollingExecutor; - private ExecutorService snmpResponseProcessingExecutor; + private ScheduledExecutorService queryingExecutor; + private ExecutorService responseProcessingExecutor; + + private final Map> responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); @Value("${transport.snmp.response_processing.parallelism_level}") private Integer responseProcessingParallelismLevel; @@ -75,10 +98,12 @@ public class SnmpTransportService implements TbTransportService { private void init() throws IOException { log.info("Initializing SNMP transport service"); - pollingExecutor = Executors.newScheduledThreadPool(1, ThingsBoardThreadFactory.forName("snmp-polling")); - snmpResponseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); + queryingExecutor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("snmp-querying")); + responseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); initializeSnmp(); + initializeTrapsListener(); + configureResponseProcessors(); log.info("SNMP transport service initialized"); } @@ -99,40 +124,118 @@ public class SnmpTransportService implements TbTransportService { snmp.listen(); } - @AfterStartUp(order = 10) - public void startPolling() { - log.info("Starting SNMP polling"); + private void initializeTrapsListener() throws IOException { + int trapsListeningPort = 1062; + String bindingAddress = "0.0.0.0/" + trapsListeningPort; - pollingExecutor.scheduleWithFixedDelay(() -> { - snmpTransportContext.getSessions().forEach(this::executeSnmpRequest); - }, 0, 1, TimeUnit.SECONDS); - } + TransportMapping transportMapping; + switch (snmpUnderlyingProtocol) { + case "udp": + transportMapping = new DefaultUdpTransportMapping(new UdpAddress(bindingAddress)); + break; + case "tcp": + transportMapping = new DefaultTcpTransportMapping(new TcpAddress(bindingAddress)); + break; + default: + throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported"); + } + + + Snmp trapsSnmp = new Snmp(transportMapping); + trapsSnmp.addCommandResponder(this); - private void executeSnmpRequest(DeviceSessionContext sessionContext) { - long timeNow = System.currentTimeMillis(); - long nextRequestExecutionTime = sessionContext.getPreviousRequestExecutedAt() + sessionContext.getProfileTransportConfiguration().getPollPeriodMs(); + transportMapping.listen(); + } - if (nextRequestExecutionTime < timeNow) { - sessionContext.setPreviousRequestExecutedAt(timeNow); + public void createQueryingTasks(DeviceSessionContext sessionContext) { + List> queryingTasks = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() + .filter(config -> config.getSpec().isRepeatingQuerying()) + .map(config -> { + RepeatingQueryingSnmpCommunicationConfig repeatingCommunicationConfig = (RepeatingQueryingSnmpCommunicationConfig) config; + return createQueryingTaskForConfig(sessionContext, repeatingCommunicationConfig); + }) + .collect(Collectors.toList()); + sessionContext.setQueryingTasks(queryingTasks); + } - DeviceProfileId deviceProfileId = sessionContext.getDeviceProfile().getId(); - snmpTransportContext.getProfilesPdus().get(deviceProfileId).forEach(pdu -> { - try { - log.debug("[{}] Sending SNMP message for device {}", pdu.getRequestID(), sessionContext.getDeviceId()); - snmp.send(pdu, sessionContext.getTarget(), deviceProfileId, sessionContext); - } catch (Exception e) { - log.error("Failed to send SNMP request: {}", e.getMessage()); + private ScheduledFuture createQueryingTaskForConfig(DeviceSessionContext sessionContext, RepeatingQueryingSnmpCommunicationConfig communicationConfig) { + Long queryingFrequency = communicationConfig.getQueryingFrequencyMs(); + return queryingExecutor.scheduleWithFixedDelay(() -> { + try { + if (sessionContext.isActive()) { + sendRequest(sessionContext, communicationConfig); } - }); + } catch (Exception e) { + log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.getMessage()); + } + }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); + } + + public void cancelQueryingTasks(DeviceSessionContext sessionContext) { + sessionContext.getQueryingTasks().forEach(task -> task.cancel(true)); + sessionContext.getQueryingTasks().clear(); + } + + public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) throws IOException { + PDU request = createPdu(communicationConfig); + executeRequest(sessionContext, request); + } + + public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) throws IOException { + PDU request = createPduWithValues(communicationConfig, values); + executeRequest(sessionContext, request); + } + + private void executeRequest(DeviceSessionContext sessionContext, PDU request) throws IOException { + if (request.size() > 0) { + log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings()); + snmp.send(request, sessionContext.getTarget(), sessionContext.getDeviceProfile().getId(), sessionContext); } } - public void onNewDeviceResponse(DeviceSessionContext sessionContext, ResponseEvent responseEvent) { - ((Snmp) responseEvent.getSource()).cancel(responseEvent.getRequest(), sessionContext); - snmpResponseProcessingExecutor.submit(() -> processSnmpResponse(sessionContext, responseEvent)); + private PDU createPdu(SnmpCommunicationConfig communicationConfig) { + PDU pdu = new PDU(); + pdu.setType(communicationConfig.getMethod().getCode()); + pdu.addAll(communicationConfig.getMappings().stream() + .map(mapping -> new VariableBinding(new OID(mapping.getOid()))) + .collect(Collectors.toList())); + return pdu; } - private void processSnmpResponse(DeviceSessionContext sessionContext, ResponseEvent event) { + private PDU createPduWithValues(SnmpCommunicationConfig communicationConfig, Map values) { + PDU pdu = new PDU(); + pdu.setType(communicationConfig.getMethod().getCode()); + pdu.addAll(communicationConfig.getMappings().stream() + .filter(mapping -> values.containsKey(mapping.getKey())) + .map(mapping -> { + String value = values.get(mapping.getKey()); + return new VariableBinding(new OID(mapping.getOid()), new OctetString(value)); + }) + .collect(Collectors.toList())); + return pdu; + } + + + private void processTrap(CommandResponderEvent event) { + if (event.getPDU().getType() != PDU.TRAP) return; + + snmpTransportContext.getSessions().stream() + .filter(sessionContext -> { + // TODO: SNMP v3 support + return sessionContext.getTarget().getSecurityName().equals(OctetString.fromByteArray(event.getSecurityName())) && + sessionContext.getTarget().getAddress().equals(event.getPeerAddress()); + }) + .findFirst() + .ifPresentOrElse(sessionContext -> { + responseProcessingExecutor.execute(() -> processResponse(sessionContext, event.getPDU())); + }, () -> { + log.debug("SNMP event is from unknown source: {}", event); + }); + } + + public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) { + ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); + if (event.getError() != null) { log.warn("Response error: {}", event.getError().getMessage(), event.getError()); return; @@ -143,16 +246,29 @@ public class SnmpTransportService implements TbTransportService { log.warn("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); return; } - DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); log.debug("[{}] Processing SNMP response for device {} with device profile {}: {}", response.getRequestID(), sessionContext.getDeviceId(), deviceProfileId, response); - JsonObject telemetry = new JsonObject(); - JsonObject attributes = new JsonObject(); + responseProcessingExecutor.execute(() -> processResponse(sessionContext, response)); + } - for (int i = 0; i < response.size(); i++) { - VariableBinding variableBinding = response.get(i); + private void processResponse(DeviceSessionContext sessionContext, PDU responsePdu) { + Map mappings = new HashMap<>(); + Map configs = new HashMap<>(); + Map responses = new EnumMap<>(SnmpCommunicationSpec.class); + + for (SnmpCommunicationConfig config : sessionContext.getProfileTransportConfiguration().getCommunicationConfigs()) { + for (SnmpMapping mapping : config.getMappings()) { + OID oid = new OID(mapping.getOid()); + mappings.put(oid, mapping); + configs.put(oid, config); + } + responses.put(config.getSpec(), new JsonObject()); + } + + for (int i = 0; i < responsePdu.size(); i++) { + VariableBinding variableBinding = responsePdu.get(i); log.trace("Processing variable binding {}: {}", i, variableBinding); if (variableBinding.getVariable() instanceof Null) { @@ -160,37 +276,57 @@ public class SnmpTransportService implements TbTransportService { continue; } - snmpTransportContext.getTelemetryMapping(deviceProfileId, variableBinding.getOid()).ifPresent(mapping -> { - log.trace("Found telemetry mapping for oid {}: {}", variableBinding.getOid(), mapping); - processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), telemetry); - }); + OID oid = variableBinding.getOid(); + if (!mappings.containsKey(oid)) { + log.debug("No SNMP mapping for oid {}", oid); + continue; + } - snmpTransportContext.getAttributeMapping(deviceProfileId, variableBinding.getOid()).ifPresent(mapping -> { - log.trace("Found attribute mapping for oid {}: {}", variableBinding.getOid(), mapping); - processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), attributes); - }); - } + SnmpCommunicationSpec spec = configs.get(oid).getSpec(); + if (!responseProcessors.containsKey(spec)) { + log.debug("No response processor found for spec {}", spec); + continue; + } - if (telemetry.entrySet().isEmpty() && attributes.entrySet().isEmpty()) { - log.warn("No telemetry or attribute values is the SNMP response for device {}", sessionContext.getDeviceId()); - return; + SnmpMapping mapping = mappings.get(oid); + processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), responses.get(spec)); } - if (!telemetry.entrySet().isEmpty()) { - TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(telemetry); - transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); - log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), telemetry); + if (responses.values().stream().allMatch(response -> response.entrySet().isEmpty())) { + log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), responsePdu.getRequestID()); + return; } - if (!attributes.entrySet().isEmpty()) { - TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(attributes); - transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); - log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), attributes); - } + responses.forEach((spec, response) -> { + Optional.ofNullable(responseProcessors.get(spec)) + .ifPresent(responseProcessor -> { + responseProcessor.accept(response, sessionContext); + }); + }); reportActivity(sessionContext.getSessionInfo()); } + private void configureResponseProcessors() { + Stream.of(SnmpCommunicationSpec.TELEMETRY_QUERYING, SnmpCommunicationSpec.TELEMETRY_TRAPS_RECEIVING) + .forEach(telemetrySpec -> { + responseProcessors.put(telemetrySpec, (response, sessionContext) -> { + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); + transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); + log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), response); + }); + }); + + Stream.of(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, SnmpCommunicationSpec.CLIENT_ATTRIBUTES_TRAPS_RECEIVING) + .forEach(clientAttributesSpec -> { + responseProcessors.put(clientAttributesSpec, (response, sessionContext) -> { + TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); + transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); + log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), response); + }); + }); + } + private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(false) @@ -200,6 +336,8 @@ public class SnmpTransportService implements TbTransportService { } private void processValue(String key, DataType dataType, String value, JsonObject result) { + if (StringUtils.isEmpty(value)) return; + switch (dataType) { case LONG: result.addProperty(key, Long.parseLong(value)); @@ -215,6 +353,11 @@ public class SnmpTransportService implements TbTransportService { } } + @Override + public void processPdu(CommandResponderEvent event) { + processTrap(event); + } + @Override public String getName() { return "SNMP"; @@ -223,11 +366,11 @@ public class SnmpTransportService implements TbTransportService { @PreDestroy public void shutdown() { log.info("Stopping SNMP transport!"); - if (pollingExecutor != null) { - pollingExecutor.shutdownNow(); + if (queryingExecutor != null) { + queryingExecutor.shutdownNow(); } - if (snmpResponseProcessingExecutor != null) { - snmpResponseProcessingExecutor.shutdownNow(); + if (responseProcessingExecutor != null) { + responseProcessingExecutor.shutdownNow(); } if (snmp != null) { try { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index 57e89ad034..c0bbc843bc 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -18,18 +18,31 @@ package org.thingsboard.server.transport.snmp.session; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; +import org.snmp4j.AbstractTarget; import org.snmp4j.CommunityTarget; import org.snmp4j.Target; +import org.snmp4j.UserTarget; import org.snmp4j.event.ResponseEvent; import org.snmp4j.event.ResponseListener; +import org.snmp4j.security.AuthSHA; +import org.snmp4j.security.PrivDES; +import org.snmp4j.security.SecurityLevel; +import org.snmp4j.security.SecurityModel; +import org.snmp4j.security.SecurityModels; +import org.snmp4j.security.USM; +import org.snmp4j.security.UsmUser; import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -40,8 +53,13 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseM import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.service.SnmpTransportService; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; @Slf4j public class DeviceSessionContext extends DeviceAwareSessionContext implements SessionMsgListener, ResponseListener { @@ -64,10 +82,16 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Setter private long previousRequestExecutedAt = 0; private final AtomicInteger msgIdSeq = new AtomicInteger(0); + @Getter private boolean isActive = true; private final String snmpUnderlyingProtocol; + @Getter + @Setter + private List> queryingTasks = new LinkedList<>(); + public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, + SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, SnmpDeviceTransportConfiguration deviceTransportConfiguration, SnmpTransportContext snmpTransportContext, SnmpTransportService snmpTransportService, String snmpUnderlyingProtocol) { @@ -80,11 +104,11 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S this.snmpTransportContext = snmpTransportContext; this.snmpTransportService = snmpTransportService; - this.profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); + this.profileTransportConfiguration = profileTransportConfiguration; this.deviceTransportConfiguration = deviceTransportConfiguration; this.snmpUnderlyingProtocol = snmpUnderlyingProtocol; - initTarget(this.profileTransportConfiguration, this.deviceTransportConfiguration); + initializeTarget(profileTransportConfiguration, deviceTransportConfiguration); } @Override @@ -103,20 +127,62 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onResponse(ResponseEvent event) { if (isActive) { - snmpTransportService.onNewDeviceResponse(this, event); + snmpTransportService.processResponseEvent(this, event); } } - public void initTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { log.trace("Initializing target for SNMP session of device {}", device); - CommunityTarget communityTarget = new CommunityTarget(); - communityTarget.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); - communityTarget.setVersion(deviceTransportConfig.getProtocolVersion().getCode()); - communityTarget.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); - communityTarget.setTimeout(profileTransportConfig.getTimeoutMs()); - communityTarget.setRetries(profileTransportConfig.getRetries()); - this.target = communityTarget; - log.info("SNMP target initialized: {}", this.target); + + AbstractTarget target; + + SnmpProtocolVersion protocolVersion = deviceTransportConfig.getProtocolVersion(); + switch (protocolVersion) { + case V1: + CommunityTarget communityTargetV1 = new CommunityTarget(); + communityTargetV1.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv1); + communityTargetV1.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); + communityTargetV1.setCommunity(new OctetString(deviceTransportConfig.getSecurityName())); + target = communityTargetV1; + break; + case V2C: + CommunityTarget communityTargetV2 = new CommunityTarget(); + communityTargetV2.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c); + communityTargetV2.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); + communityTargetV2.setCommunity(new OctetString(deviceTransportConfig.getSecurityName())); + target = communityTargetV2; + break; + case V3: + USM usm = new USM(); + SecurityModels.getInstance().addSecurityModel(usm); + + OctetString securityName = new OctetString(deviceTransportConfig.getSecurityName()); + OctetString authenticationPassphrase = new OctetString(deviceTransportConfig.getAuthenticationPassphrase()); + OctetString privacyPassphrase = new OctetString(deviceTransportConfig.getPrivacyPassphrase()); + + OID authenticationProtocol = AuthSHA.ID; + OID privacyProtocol = PrivDES.ID; // FIXME: to config + + UsmUser user = new UsmUser(securityName, authenticationProtocol, authenticationPassphrase, privacyProtocol, privacyPassphrase); + snmpTransportService.getSnmp().getUSM().addUser(user); + + UserTarget userTarget = new UserTarget(); + userTarget.setSecurityName(securityName); + userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV); + + target = userTarget; + break; + default: + throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); + } + + target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); + target.setTimeout(profileTransportConfig.getTimeoutMs()); + target.setRetries(profileTransportConfig.getRetries()); + target.setVersion(protocolVersion.getCode()); + + this.target = target; + log.info("SNMP target initialized: {}", target); } public void close() { @@ -138,6 +204,21 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { + profileTransportConfiguration.getCommunicationConfigs().stream() + .filter(config -> config.getSpec() == SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) + .findFirst() + .ifPresent(communicationConfig -> { + Map sharedAttributes = JsonConverter.toJson(attributeUpdateNotification).entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() + )); + try { + snmpTransportService.sendRequest(this, communicationConfig, sharedAttributes); + } catch (Exception e) { + log.error("Failed to send request with shared attributes to SNMP device {}: {}", getDeviceId(), e.getMessage()); + } + }); } @Override diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java similarity index 69% rename from common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java rename to common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java index 29fa35dcab..d48eaadce3 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulator.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java @@ -15,7 +15,10 @@ */ package org.thingsboard.server.transport.snmp; +import org.snmp4j.CommandResponderEvent; import org.snmp4j.CommunityTarget; +import org.snmp4j.PDU; +import org.snmp4j.Snmp; import org.snmp4j.Target; import org.snmp4j.TransportMapping; import org.snmp4j.agent.BaseAgent; @@ -42,45 +45,81 @@ import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.Integer32; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.Variable; +import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.TransportMappings; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.Scanner; +import java.util.function.Consumer; +import java.util.stream.Collectors; -public class SnmpDeviceSimulator extends BaseAgent { +public class SnmpDeviceSimulatorV2 extends BaseAgent { + + public static class RequestProcessor extends CommandProcessor { + private final Consumer processor; + + public RequestProcessor(Consumer processor) { + super(new OctetString(MPv3.createLocalEngineID())); + this.processor = processor; + } + + @Override + public void processPdu(CommandResponderEvent event) { + processor.accept(event); + } + } public static void main(String[] args) throws IOException { - SnmpDeviceSimulator device = new SnmpDeviceSimulator(1610); + SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public"); device.start(); device.setUpMappings(Map.of( ".1.3.6.1.2.1.1.1.50", "12", ".1.3.6.1.2.1.2.1.52", "56", - ".1.3.6.1.2.1.3.1.54", "yes" + ".1.3.6.1.2.1.3.1.54", "yes", + ".1.3.6.1.2.1.7.1.58", "" )); + +// while (true) { +// new Scanner(System.in).nextLine(); +// device.sendTrap("127.0.0.1", 1062, Map.of(".1.3.6.1.2.87.1.56", "12")); +// System.out.println("sent"); +// } + +// Snmp snmp = new Snmp(device.transportMappings[0]); +// device.snmp.addCommandResponder(event -> { +// System.out.println(event); +// }); + new Scanner(System.in).nextLine(); } private final Target target; private final Address address; + private Snmp snmp; - public SnmpDeviceSimulator(int port) { - super(new File("conf.agent"), new File("bootCounter.agent"), - new CommandProcessor(new OctetString(MPv3.createLocalEngineID()))); + private final String password; + public SnmpDeviceSimulatorV2(int port, String password) throws IOException { + super(new File("conf.agent"), new File("bootCounter.agent"), new RequestProcessor(event -> { + System.out.println("aboba"); + ((Snmp) event.getSource()).cancel(event.getPDU(), event1 -> System.out.println("canceled")); + })); CommunityTarget target = new CommunityTarget(); - target.setCommunity(new OctetString("public")); - address = GenericAddress.parse("udp:0.0.0.0/" + port); + target.setCommunity(new OctetString(password)); + this.address = GenericAddress.parse("udp:0.0.0.0/" + port); target.setAddress(address); target.setRetries(2); target.setTimeout(1500); target.setVersion(SnmpConstants.version2c); this.target = target; + this.password = password; } public void start() throws IOException { @@ -90,17 +129,27 @@ public class SnmpDeviceSimulator extends BaseAgent { finishInit(); run(); sendColdStartNotification(); + snmp = new Snmp(transportMappings[0]); } public void setUpMappings(Map oidToResponseMappings) { unregisterManagedObject(getSnmpv2MIB()); oidToResponseMappings.forEach((oid, response) -> { - registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_ONLY, new OctetString(response))); + registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_WRITE, new OctetString(response))); }); } - public Target getTarget() { - return target; + public void sendTrap(String host, int port, Map values) throws IOException { + PDU pdu = new PDU(); + pdu.addAll(values.entrySet().stream() + .map(entry -> new VariableBinding(new OID(entry.getKey()), new OctetString(entry.getValue()))) + .collect(Collectors.toList())); + pdu.setType(PDU.TRAP); + + CommunityTarget remoteTarget = (CommunityTarget) getTarget().clone(); + remoteTarget.setAddress(new UdpAddress(host + "/" + port)); + + snmp.send(pdu, remoteTarget); } @Override @@ -165,4 +214,9 @@ public class SnmpDeviceSimulator extends BaseAgent { new OctetString("public2public").toSubIndex(true), com2sec); communityMIB.getSnmpCommunityEntry().addRow(row); } + + public Target getTarget() { + return target; + } + } diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java new file mode 100644 index 0000000000..63ccd7021a --- /dev/null +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java @@ -0,0 +1,52 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import org.snmp4j.UserTarget; +import org.snmp4j.security.AuthSHA; +import org.snmp4j.security.PrivDES; +import org.snmp4j.security.SecurityLevel; +import org.snmp4j.security.SecurityModels; +import org.snmp4j.security.USM; +import org.snmp4j.security.UsmUser; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; + +import java.io.IOException; + +public class SnmpDeviceSimulatorV3 extends SnmpDeviceSimulatorV2 { + public SnmpDeviceSimulatorV3(int port, String securityName, String authenticationPassphrase, String privacyPassphrase) throws IOException { + super(12, null); +// super(new File("conf.agent"), new File("bootCounter.agent")); + + USM usm = new USM(); + SecurityModels.getInstance().addSecurityModel(usm); + + OID authenticationProtocol = AuthSHA.ID; + OID privacyProtocol = PrivDES.ID; + + UsmUser user = new UsmUser(new OctetString(securityName), authenticationProtocol, new OctetString(authenticationPassphrase), privacyProtocol, new OctetString(privacyPassphrase)); + } + + public void initV3(UsmUser user, String securityName) { +// snmp.getUSM().addUser(user); + + UserTarget userTarget = new UserTarget(); + userTarget.setSecurityName(new OctetString(securityName)); + userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV); + } + +} diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java new file mode 100644 index 0000000000..29a8c085c8 --- /dev/null +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import java.io.IOException; +import java.util.Map; +import java.util.Scanner; + +public class SnmpTest { + public static void main(String[] args) throws IOException { + SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public"); + + device.start(); + device.setUpMappings(Map.of( + ".1.3.6.1.2.1.1.1.50", "12", + ".1.3.6.1.2.1.2.1.52", "56", + ".1.3.6.1.2.1.3.1.54", "yes", + ".1.3.6.1.2.1.7.1.58", "" + )); + + +// while (true) { +// new Scanner(System.in).nextLine(); +// device.sendTrap("127.0.0.1", 1062, Map.of(".1.3.6.1.2.87.1.56", "12")); +// System.out.println("sent"); +// } + +// Snmp snmp = new Snmp(device.transportMappings[0]); +// device.snmp.addCommandResponder(event -> { +// System.out.println(event); +// }); + + new Scanner(System.in).nextLine(); + } + +} diff --git a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json index 78b66c4c0e..f8291a090b 100644 --- a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json +++ b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json @@ -1,27 +1,53 @@ { - "pollPeriodMs": 3000, "timeoutMs": 500, "retries": 0, - "telemetryMappings": [ + "communicationConfigs": [ { - "oid": ".1.3.6.1.2.1.1.1.50", - "method": "GET", - "key": "temperature", - "dataType": "LONG" + "spec": "TELEMETRY_QUERYING", + "queryingFrequencyMs": 3000, + "mappings": [ + { + "oid": ".1.3.6.1.2.1.1.1.50", + "key": "temperature", + "dataType": "LONG" + }, + { + "oid": ".1.3.6.1.2.1.2.1.52", + "key": "humidity", + "dataType": "DOUBLE" + } + ] }, { - "oid": ".1.3.6.1.2.1.2.1.52", - "method": "GET", - "key": "humidity", - "dataType": "LONG" - } - ], - "attributesMappings": [ + "spec": "CLIENT_ATTRIBUTES_QUERYING", + "queryingFrequencyMs": 5000, + "mappings": [ + { + "oid": ".1.3.6.1.2.1.3.1.54", + "key": "isCool", + "dataType": "STRING" + } + ] + }, + { + "spec": "SHARED_ATTRIBUTES_SETTING", + "mappings": [ + { + "oid": ".1.3.6.1.2.1.7.1.58", + "key": "shared", + "dataType": "STRING" + } + ] + }, { - "oid": ".1.3.6.1.2.1.3.1.54", - "method": "GET", - "key": "isCool", - "dataType": "STRING" + "spec": "TELEMETRY_TRAPS_RECEIVING", + "mappings": [ + { + "oid": ".1.3.6.1.2.8.7.1.56", + "key": "temperature_trap", + "dataType": "LONG" + } + ] } ] -} \ No newline at end of file +} diff --git a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json index c73d817bfb..9a97b75e40 100644 --- a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json +++ b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json @@ -1,6 +1,6 @@ { "address": "127.0.0.1", "port": 1610, - "community": "public", + "password": "public", "protocolVersion": "V2C" } \ No newline at end of file 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 2c5dfafe4e..699e43e5ee 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 @@ -285,7 +285,7 @@ public class JsonConverter { return result; } - public static JsonElement toJson(AttributeUpdateNotificationMsg payload) { + public static JsonObject toJson(AttributeUpdateNotificationMsg payload) { JsonObject result = new JsonObject(); if (payload.getSharedUpdatedCount() > 0) { payload.getSharedUpdatedList().forEach(addToObjectFromProto(result)); From b51fee8740223f8bd6172c211d409f30b947ccd0 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Apr 2021 10:30:30 +0300 Subject: [PATCH 010/127] created firmware dao --- .../server/controller/BaseController.java | 42 +++ .../server/controller/FirmwareController.java | 147 ++++++++++ .../controller/TbResourceController.java | 8 +- .../service/security/permission/Resource.java | 3 +- .../permission/TenantAdminPermissions.java | 1 + .../server/dao/firmware/FirmwareService.java | 39 +++ .../dao/resource/TbResourceService.java | 2 +- .../server/common/data/Device.java | 14 + .../server/common/data/DeviceProfile.java | 3 + .../server/common/data/EntityType.java | 2 +- .../server/common/data/Firmware.java | 44 +++ .../server/common/data/FirmwareInfo.java | 56 ++++ .../server/common/data/TbResourceInfo.java | 2 + .../server/common/data/id/FirmwareId.java | 44 +++ .../dao/firmware/BaseFirmwareService.java | 162 +++++++++++ .../server/dao/firmware/FirmwareDao.java | 23 ++ .../server/dao/firmware/FirmwareInfoDao.java | 30 ++ .../server/dao/model/ModelConstants.java | 12 + .../dao/model/sql/AbstractDeviceEntity.java | 11 + .../dao/model/sql/DeviceProfileEntity.java | 12 + .../server/dao/model/sql/FirmwareEntity.java | 99 +++++++ .../dao/model/sql/FirmwareInfoEntity.java | 92 ++++++ .../sql/firmware/FirmwareInfoRepository.java | 34 +++ .../dao/sql/firmware/FirmwareRepository.java | 24 ++ .../dao/sql/firmware/JpaFirmwareDao.java | 46 +++ .../dao/sql/firmware/JpaFirmwareInfoDao.java | 59 ++++ .../sql/resource/TbResourceRepository.java | 2 - .../resources/sql/schema-entities-hsql.sql | 19 +- .../main/resources/sql/schema-entities.sql | 21 +- .../server/dao/SqlDaoServiceTestSuite.java | 2 +- .../dao/service/AbstractServiceTest.java | 6 +- .../dao/service/BaseFirmwareServiceTest.java | 271 ++++++++++++++++++ .../service/sql/FirmwareServiceSqlTest.java | 23 ++ .../resources/sql/hsql/drop-all-tables.sql | 1 + .../resources/sql/psql/drop-all-tables.sql | 1 + .../sql/timescale/drop-all-tables.sql | 4 +- 36 files changed, 1341 insertions(+), 20 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/controller/FirmwareController.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 04ea69d419..4734361770 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.ExceptionHandler; @@ -37,6 +38,8 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityViewInfo; +import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.TbResourceInfo; @@ -61,6 +64,7 @@ import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -97,6 +101,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.oauth2.OAuth2Service; @@ -232,6 +237,9 @@ public abstract class BaseController { @Autowired protected TbResourceService resourceService; + @Autowired + protected FirmwareService firmwareService; + @Autowired protected TbQueueProducerProvider producerProvider; @@ -470,6 +478,9 @@ public abstract class BaseController { case TB_RESOURCE: checkResourceId(new TbResourceId(entityId.getId()), operation); return; + case FIRMWARE: + checkFirmwareId(new FirmwareId(entityId.getId()), operation); + return; default: throw new IllegalArgumentException("Unsupported entity type: " + entityId.getEntityType()); } @@ -701,6 +712,30 @@ public abstract class BaseController { } } + Firmware checkFirmwareId(FirmwareId firmwareId, Operation operation) throws ThingsboardException { + try { + validateId(firmwareId, "Incorrect firmwareId " + firmwareId); + Firmware firmware = firmwareService.findFirmwareById(getCurrentUser().getTenantId(), firmwareId); + checkNotNull(firmware); + accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, firmwareId, firmware); + return firmware; + } catch (Exception e) { + throw handleException(e, false); + } + } + + FirmwareInfo checkFirmwareInfoId(FirmwareId firmwareId, Operation operation) throws ThingsboardException { + try { + validateId(firmwareId, "Incorrect firmwareId " + firmwareId); + FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(getCurrentUser().getTenantId(), firmwareId); + checkNotNull(firmwareInfo); + accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmwareInfo); + return firmwareInfo; + } catch (Exception e) { + throw handleException(e, false); + } + } + @SuppressWarnings("unchecked") protected I emptyId(EntityType entityType) { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); @@ -924,4 +959,11 @@ public abstract class BaseController { } } + protected MediaType parseMediaType(String contentType) { + try { + return MediaType.parseMediaType(contentType); + } catch (Exception e) { + return MediaType.APPLICATION_OCTET_STREAM; + } + } } diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java new file mode 100644 index 0000000000..7736abba11 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -0,0 +1,147 @@ +/** + * Copyright © 2016-2021 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.controller; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; +import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.security.permission.Operation; + +import java.nio.ByteBuffer; + +@Slf4j +@RestController +@TbCoreComponent +@RequestMapping("/api") +public class FirmwareController extends BaseController { + + public static final String FIRMWARE_ID = "firmwareId"; + + @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')") + @RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET) + @ResponseBody + public ResponseEntity downloadFirmware(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException { + checkParameter(FIRMWARE_ID, strFirmwareId); + try { + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); + Firmware firmware = checkFirmwareId(firmwareId, Operation.READ); + + ByteArrayResource resource = new ByteArrayResource(firmware.getData().array()); + return ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmware.getFileName()) + .header("x-filename", firmware.getFileName()) + .contentLength(resource.contentLength()) + .contentType(parseMediaType(firmware.getContentType())) + .body(resource); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET) + @ResponseBody + public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException { + checkParameter(FIRMWARE_ID, strFirmwareId); + try { + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); + return checkFirmwareInfoId(firmwareId, Operation.READ); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.GET) + @ResponseBody + public Firmware getFirmwareById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException { + checkParameter(FIRMWARE_ID, strFirmwareId); + try { + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); + return checkFirmwareId(firmwareId, Operation.READ); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/firmware", method = RequestMethod.POST) + @ResponseBody + public Firmware saveFirmware(@RequestParam("title") String title, + @RequestBody MultipartFile firmwareFile) throws ThingsboardException { + checkParameter("title", title); + try { + checkNotNull(firmwareFile); + Firmware firmware = new Firmware(); + firmware.setTenantId(getTenantId()); + firmware.setTitle(title); + firmware.setFileName(firmwareFile.getOriginalFilename()); + firmware.setContentType(firmwareFile.getContentType()); + firmware.setData(ByteBuffer.wrap(firmwareFile.getBytes())); + return firmwareService.saveFirmware(firmware); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/firmware", method = RequestMethod.GET) + @ResponseBody + public PageData getFirmwares(@RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(firmwareService.findTenantFirmwaresByTenantId(getTenantId(), pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE) + @ResponseBody + public void deleteResource(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException { + checkParameter(FIRMWARE_ID, strFirmwareId); + try { + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); + checkFirmwareInfoId(firmwareId, Operation.DELETE); + firmwareService.deleteFirmware(getTenantId(), firmwareId); + } catch (Exception e) { + throw handleException(e); + } + } + +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index 0190c3e790..adba9827cd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -55,12 +55,6 @@ public class TbResourceController extends BaseController { public static final String RESOURCE_ID = "resourceId"; - private final TbResourceService resourceService; - - public TbResourceController(TbResourceService resourceService) { - this.resourceService = resourceService; - } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/resource/{resourceId}/download", method = RequestMethod.GET) @ResponseBody @@ -187,7 +181,7 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource/{resourceId}", method = RequestMethod.DELETE) @ResponseBody public void deleteResource(@PathVariable("resourceId") String strResourceId) throws ThingsboardException { - checkParameter("resourceId", strResourceId); + checkParameter(RESOURCE_ID, strResourceId); try { TbResourceId resourceId = new TbResourceId(toUUID(strResourceId)); TbResource tbResource = checkResourceId(resourceId, Operation.DELETE); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index ba53ceea98..b6ac71169e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -37,7 +37,8 @@ public enum Resource { TENANT_PROFILE(EntityType.TENANT_PROFILE), DEVICE_PROFILE(EntityType.DEVICE_PROFILE), API_USAGE_STATE(EntityType.API_USAGE_STATE), - TB_RESOURCE(EntityType.TB_RESOURCE); + TB_RESOURCE(EntityType.TB_RESOURCE), + FIRMWARE(EntityType.FIRMWARE); private final EntityType entityType; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index c36a70821f..a6fce632b8 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -42,6 +42,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker); put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker); put(Resource.TB_RESOURCE, tbResourcePermissionChecker); + put(Resource.FIRMWARE, tenantEntityPermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java new file mode 100644 index 0000000000..bff1a6f806 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2021 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.dao.firmware; + +import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; + + +public interface FirmwareService { + + Firmware saveFirmware(Firmware firmware); + + Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); + + FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId); + + PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink); + + void deleteFirmware(TenantId tenantId, FirmwareId firmwareId); + + void deleteFirmwaresByTenantId(TenantId tenantId); +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java index 0093b43dfe..d879827008 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java @@ -32,7 +32,7 @@ import java.util.List; public interface TbResourceService { TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException; - TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); + TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceKey); TbResource findResourceById(TenantId tenantId, TbResourceId resourceId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index ad93983cac..54afd813c7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import java.io.ByteArrayInputStream; @@ -44,6 +45,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen @JsonIgnore private byte[] deviceDataBytes; + private FirmwareId firmwareId; + public Device() { super(); } @@ -61,6 +64,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen this.label = device.getLabel(); this.deviceProfileId = device.getDeviceProfileId(); this.setDeviceData(device.getDeviceData()); + this.firmwareId = device.getFirmwareId(); } public Device updateDevice(Device device) { @@ -155,6 +159,14 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen return getName(); } + public FirmwareId getFirmwareId() { + return firmwareId; + } + + public void setFirmwareId(FirmwareId firmwareId) { + this.firmwareId = firmwareId; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -171,6 +183,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen builder.append(", deviceProfileId="); builder.append(deviceProfileId); builder.append(", deviceData="); + builder.append(firmwareId); + builder.append(", firmwareId="); builder.append(deviceData); builder.append(", additionalInfo="); builder.append(getAdditionalInfo()); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 8b24cefa71..8a6f50e632 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; @@ -49,6 +50,8 @@ public class DeviceProfile extends SearchTextBased implements H private byte[] profileDataBytes; private String provisionDeviceKey; + private FirmwareId firmwareId; + public DeviceProfile() { super(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index f289f802d8..36be053789 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE; + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE, TB_RESOURCE, FIRMWARE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java b/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java new file mode 100644 index 0000000000..b4155a578a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2021 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; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.FirmwareId; + +import java.nio.ByteBuffer; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Firmware extends FirmwareInfo { + + private static final long serialVersionUID = 3091601761339422546L; + + private transient ByteBuffer data; + + public Firmware() { + super(); + } + + public Firmware(FirmwareId id) { + super(id); + } + + public Firmware(Firmware firmware) { + super(firmware); + this.data = firmware.getData(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java new file mode 100644 index 0000000000..b950268649 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2021 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; +import lombok.EqualsAndHashCode; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.TenantId; + +@Slf4j +@Data +@EqualsAndHashCode(callSuper = true) +public class FirmwareInfo extends SearchTextBased implements HasTenantId { + + private static final long serialVersionUID = 3168391583570815419L; + + private TenantId tenantId; + private String title; + private String fileName; + private String contentType; + + public FirmwareInfo() { + super(); + } + + public FirmwareInfo(FirmwareId id) { + super(id); + } + + public FirmwareInfo(FirmwareInfo firmwareInfo) { + super(firmwareInfo); + this.tenantId = firmwareInfo.getTenantId(); + this.title = firmwareInfo.getTitle(); + this.fileName = firmwareInfo.getFileName(); + this.contentType = firmwareInfo.getContentType(); + } + + @Override + public String getSearchText() { + return title; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java index d72982aeca..7fa869e14f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java @@ -26,6 +26,8 @@ import org.thingsboard.server.common.data.id.TenantId; @EqualsAndHashCode(callSuper = true) public class TbResourceInfo extends SearchTextBased implements HasTenantId { + private static final long serialVersionUID = 7282664529021651736L; + private TenantId tenantId; private String title; private ResourceType resourceType; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java new file mode 100644 index 0000000000..3cdee53f58 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/FirmwareId.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2021 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.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class FirmwareId extends UUIDBased implements EntityId { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public FirmwareId(@JsonProperty("id") UUID id) { + super(id); + } + + public static FirmwareId fromString(String firmwareId) { + return new FirmwareId(UUID.fromString(firmwareId)); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.FIRMWARE; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java new file mode 100644 index 0000000000..bf1c4f10e6 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -0,0 +1,162 @@ +/** + * Copyright © 2016-2021 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.dao.firmware; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.hibernate.exception.ConstraintViolationException; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.tenant.TenantDao; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validatePageLink; + +@Service +@Slf4j +public class BaseFirmwareService implements FirmwareService { + public static final String INCORRECT_FIRMWARE_ID = "Incorrect firmwareId "; + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + + private final TenantDao tenantDao; + private final FirmwareDao firmwareDao; + private final FirmwareInfoDao firmwareInfoDao; + + public BaseFirmwareService(TenantDao tenantDao, FirmwareDao firmwareDao, FirmwareInfoDao firmwareInfoDao) { + this.tenantDao = tenantDao; + this.firmwareDao = firmwareDao; + this.firmwareInfoDao = firmwareInfoDao; + } + + @Override + public Firmware saveFirmware(Firmware firmware) { + log.trace("Executing saveFirmware [{}]", firmware); + firmwareValidator.validate(firmware, Firmware::getTenantId); + return firmwareDao.save(firmware.getTenantId(), firmware); + } + + @Override + public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) { + log.trace("Executing findFirmwareById [{}]", firmwareId); + validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); + return firmwareDao.findById(tenantId, firmwareId.getId()); + } + + @Override + public FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId) { + log.trace("Executing findFirmwareInfoById [{}]", firmwareId); + validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); + return firmwareInfoDao.findById(tenantId, firmwareId.getId()); + } + + @Override + public PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink) { + log.trace("Executing findTenantFirmwaresByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + return firmwareInfoDao.findFirmwareInfoByTenantId(tenantId, pageLink); + } + + @Override + public void deleteFirmware(TenantId tenantId, FirmwareId firmwareId) { + log.trace("Executing deleteFirmware [{}]", firmwareId); + validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); + try { + firmwareDao.removeById(tenantId, firmwareId.getId()); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device")) { + throw new DataValidationException("The firmware referenced by the devices cannot be deleted!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device_profile")) { + throw new DataValidationException("The firmware referenced by the device profile cannot be deleted!"); + } else { + throw t; + } + } + } + + @Override + public void deleteFirmwaresByTenantId(TenantId tenantId) { + log.trace("Executing deleteFirmwaresByTenantId, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + tenantFirmwareRemover.removeEntities(tenantId, tenantId); + } + + private DataValidator firmwareValidator = new DataValidator<>() { + + @Override + protected void validateDataImpl(TenantId tenantId, Firmware firmware) { + if (firmware.getTenantId() == null) { + throw new DataValidationException("Firmware should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Firmware is referencing to non-existent tenant!"); + } + } + + if (StringUtils.isEmpty(firmware.getTitle())) { + throw new DataValidationException("Firmware title should be specified!"); + } + if (StringUtils.isEmpty(firmware.getFileName())) { + throw new DataValidationException("Firmware file name should be specified!"); + } + if (StringUtils.isEmpty(firmware.getContentType())) { + throw new DataValidationException("Firmware content type should be specified!"); + } + ByteBuffer data = firmware.getData(); + if (data == null || !data.hasArray() || data.array().length == 0) { + throw new DataValidationException("Firmware data should be specified!"); + } + } + }; + + private PaginatedRemover tenantFirmwareRemover = + new PaginatedRemover<>() { + + @Override + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { + return firmwareInfoDao.findFirmwareInfoByTenantId(id, pageLink); + } + + @Override + protected void removeEntity(TenantId tenantId, FirmwareInfo entity) { + deleteFirmware(tenantId, entity.getId()); + } + }; + + protected Optional extractConstraintViolationException(Exception t) { + if (t instanceof ConstraintViolationException) { + return Optional.of((ConstraintViolationException) t); + } else if (t.getCause() instanceof ConstraintViolationException) { + return Optional.of((ConstraintViolationException) (t.getCause())); + } else { + return Optional.empty(); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java new file mode 100644 index 0000000000..0cacb47ea0 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareDao.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 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.dao.firmware; + +import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.dao.Dao; + +public interface FirmwareDao extends Dao { + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java new file mode 100644 index 0000000000..cf8259c13c --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2021 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.dao.firmware; + +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.Dao; + +import java.util.UUID; + +public interface FirmwareInfoDao extends Dao { + + PageData findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink); + +} 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 3afae3c081..05af88eeb9 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 @@ -153,6 +153,7 @@ public class ModelConstants { public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; public static final String DEVICE_DEVICE_PROFILE_ID_PROPERTY = "device_profile_id"; public static final String DEVICE_DEVICE_DATA_PROPERTY = "device_data"; + public static final String DEVICE_FIRMWARE_ID_PROPERTY = "firmware_id"; public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; @@ -176,6 +177,7 @@ public class ModelConstants { public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name"; public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; + public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id"; /** * Cassandra entityView constants. @@ -467,6 +469,16 @@ public class ModelConstants { public static final String RESOURCE_FILE_NAME_COLUMN = "file_name"; public static final String RESOURCE_DATA_COLUMN = "data"; + /** + * Firmware constants. + */ + public static final String FIRMWARE_TABLE_NAME = "firmware"; + public static final String FIRMWARE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; + public static final String FIRMWARE_TITLE_COLUMN = TITLE_PROPERTY; + public static final String FIRMWARE_FILE_NAME_COLUMN = "file_name"; + public static final String FIRMWARE_CONTENT_TYPE_COLUMN = "content_type"; + public static final String FIRMWARE_DATA_COLUMN = "data"; + /** * Cassandra attributes and timeseries constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index 5a4381fa24..8dee576611 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; @@ -73,6 +74,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti @Column(name = ModelConstants.DEVICE_DEVICE_PROFILE_ID_PROPERTY, columnDefinition = "uuid") private UUID deviceProfileId; + @Column(name = ModelConstants.DEVICE_FIRMWARE_ID_PROPERTY, columnDefinition = "uuid") + private UUID firmwareId; + @Type(type = "jsonb") @Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode deviceData; @@ -95,6 +99,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti if (device.getDeviceProfileId() != null) { this.deviceProfileId = device.getDeviceProfileId().getId(); } + if (device.getFirmwareId() != null) { + this.firmwareId = device.getFirmwareId().getId(); + } this.deviceData = JacksonUtil.convertValue(device.getDeviceData(), ObjectNode.class); this.name = device.getName(); this.type = device.getType(); @@ -114,6 +121,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti this.label = deviceEntity.getLabel(); this.searchText = deviceEntity.getSearchText(); this.additionalInfo = deviceEntity.getAdditionalInfo(); + this.firmwareId = deviceEntity.getFirmwareId(); } @Override @@ -138,6 +146,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti if (deviceProfileId != null) { device.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); } + if (firmwareId != null) { + device.setFirmwareId(new FirmwareId(firmwareId)); + } device.setDeviceData(JacksonUtil.convertValue(deviceData, DeviceData.class)); device.setName(name); device.setType(type); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 0ddf78c8ce..e437c87fdd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -89,6 +90,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY) private String provisionDeviceKey; + @Column(name=ModelConstants.DEVICE_PROFILE_FIRMWARE_ID_PROPERTY) + private UUID firmwareId; + public DeviceProfileEntity() { super(); } @@ -113,6 +117,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl } this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); + if (deviceProfile.getFirmwareId() != null) { + this.firmwareId = deviceProfile.getFirmwareId().getId(); + } } @Override @@ -148,6 +155,11 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl } deviceProfile.setDefaultQueueName(defaultQueueName); deviceProfile.setProvisionDeviceKey(provisionDeviceKey); + + if (firmwareId != null) { + deviceProfile.setFirmwareId(new FirmwareId(firmwareId)); + } + return deviceProfile; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java new file mode 100644 index 0000000000..1823c5eae5 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -0,0 +1,99 @@ +/** + * Copyright © 2016-2021 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.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.SearchTextEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.nio.ByteBuffer; +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = FIRMWARE_TABLE_NAME) +public class FirmwareEntity extends BaseSqlEntity implements SearchTextEntity { + + @Column(name = FIRMWARE_TENANT_ID_COLUMN) + private UUID tenantId; + + @Column(name = FIRMWARE_TITLE_COLUMN) + private String title; + + @Column(name = FIRMWARE_FILE_NAME_COLUMN) + private String fileName; + + @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) + private String contentType; + + @Column(name = FIRMWARE_DATA_COLUMN, columnDefinition = "BINARY") + private byte[] data; + + @Column(name = SEARCH_TEXT_PROPERTY) + private String searchText; + + public FirmwareEntity() { + super(); + } + + public FirmwareEntity(Firmware firmware) { + this.createdTime = firmware.getCreatedTime(); + this.setUuid(firmware.getUuidId()); + this.tenantId = firmware.getTenantId().getId(); + this.title = firmware.getTitle(); + this.fileName = firmware.getFileName(); + this.contentType = firmware.getContentType(); + this.data = firmware.getData().array(); + } + + @Override + public String getSearchTextSource() { + return title; + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + @Override + public Firmware toData() { + Firmware firmware = new Firmware(new FirmwareId(id)); + firmware.setCreatedTime(createdTime); + firmware.setTenantId(new TenantId(tenantId)); + firmware.setTitle(title); + firmware.setFileName(fileName); + firmware.setContentType(contentType); + firmware.setData(ByteBuffer.wrap(data)); + return firmware; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java new file mode 100644 index 0000000000..ec7ee7206f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java @@ -0,0 +1,92 @@ +/** + * Copyright © 2016-2021 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.dao.model.sql; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.SearchTextEntity; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; +import java.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@Table(name = FIRMWARE_TABLE_NAME) +public class FirmwareInfoEntity extends BaseSqlEntity implements SearchTextEntity { + + @Column(name = FIRMWARE_TENANT_ID_COLUMN) + private UUID tenantId; + + @Column(name = FIRMWARE_TITLE_COLUMN) + private String title; + + @Column(name = FIRMWARE_FILE_NAME_COLUMN) + private String fileName; + + @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) + private String contentType; + + @Column(name = SEARCH_TEXT_PROPERTY) + private String searchText; + + public FirmwareInfoEntity() { + super(); + } + + public FirmwareInfoEntity(FirmwareInfo firmware) { + this.createdTime = firmware.getCreatedTime(); + this.setUuid(firmware.getUuidId()); + this.tenantId = firmware.getTenantId().getId(); + this.title = firmware.getTitle(); + this.fileName = firmware.getFileName(); + this.contentType = firmware.getContentType(); + } + + @Override + public String getSearchTextSource() { + return title; + } + + @Override + public void setSearchText(String searchText) { + this.searchText = searchText; + } + + @Override + public FirmwareInfo toData() { + FirmwareInfo firmware = new FirmwareInfo(new FirmwareId(id)); + firmware.setCreatedTime(createdTime); + firmware.setTenantId(new TenantId(tenantId)); + firmware.setTitle(title); + firmware.setFileName(fileName); + firmware.setContentType(contentType); + return firmware; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java new file mode 100644 index 0000000000..dd4a53fe71 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2021 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.dao.sql.firmware; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity; + +import java.util.UUID; + +public interface FirmwareInfoRepository extends CrudRepository { + @Query("SELECT fi FROM FirmwareInfoEntity fi WHERE " + + "fi.tenantId = :tenantId " + + "AND LOWER(fi.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findAllByTenantId(@Param("tenantId") UUID tenantId, + @Param("searchText") String searchText, + Pageable pageable); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java new file mode 100644 index 0000000000..a969507798 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareRepository.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2021 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.dao.sql.firmware; + +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.dao.model.sql.FirmwareEntity; + +import java.util.UUID; + +public interface FirmwareRepository extends CrudRepository { +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java new file mode 100644 index 0000000000..44b956c298 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareDao.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2021 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.dao.sql.firmware; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.dao.firmware.FirmwareDao; +import org.thingsboard.server.dao.model.sql.FirmwareEntity; +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; + +import java.util.UUID; + +@Slf4j +@Component +public class JpaFirmwareDao extends JpaAbstractSearchTextDao implements FirmwareDao { + + @Autowired + private FirmwareRepository firmwareRepository; + + @Override + protected Class getEntityClass() { + return FirmwareEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return firmwareRepository; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java new file mode 100644 index 0000000000..ae61c9ac4f --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2021 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.dao.sql.firmware; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.firmware.FirmwareInfoDao; +import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity; +import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; + +import java.util.Objects; +import java.util.UUID; + +@Slf4j +@Component +public class JpaFirmwareInfoDao extends JpaAbstractSearchTextDao implements FirmwareInfoDao { + + @Autowired + private FirmwareInfoRepository firmwareInfoRepository; + + @Override + protected Class getEntityClass() { + return FirmwareInfoEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return firmwareInfoRepository; + } + + @Override + public PageData findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(firmwareInfoRepository + .findAllByTenantId( + tenantId.getId(), + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java index 488192eb13..c5587d36b2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceRepository.java @@ -48,8 +48,6 @@ public interface TbResourceRepository extends CrudRepository implements Comparator { @Override public int compare(D o1, D o2) { @@ -192,7 +196,7 @@ public abstract class AbstractServiceTest { @Bean public AuditLogLevelFilter auditLogLevelFilter() { - Map mask = new HashMap<>(); + Map mask = new HashMap<>(); for (EntityType entityType : EntityType.values()) { mask.put(entityType.name().toLowerCase(), AuditLogLevelMask.RW.name()); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java new file mode 100644 index 0000000000..b5383a1809 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java @@ -0,0 +1,271 @@ +/** + * Copyright © 2016-2021 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.dao.service; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +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.Firmware; +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.exception.DataValidationException; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { + + public static final String TITLE = "My firmware"; + private static final String FILE_NAME = "filename.txt"; + private static final String CONTENT_TYPE = "text/plain"; + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{0}); + + private IdComparator idComparator = new IdComparator<>(); + + private TenantId tenantId; + + @Before + public void before() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + Assert.assertNotNull(savedTenant); + tenantId = savedTenant.getId(); + } + + @After + public void after() { + tenantService.deleteTenant(tenantId); + } + + @Test + public void testSaveFirmware() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + + Assert.assertNotNull(savedFirmware); + Assert.assertNotNull(savedFirmware.getId()); + Assert.assertTrue(savedFirmware.getCreatedTime() > 0); + Assert.assertEquals(firmware.getTenantId(), savedFirmware.getTenantId()); + Assert.assertEquals(firmware.getTitle(), savedFirmware.getTitle()); + Assert.assertEquals(firmware.getFileName(), savedFirmware.getFileName()); + Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType()); + Assert.assertEquals(firmware.getData(), savedFirmware.getData()); + + savedFirmware.setTitle("My new firmware"); + firmwareService.saveFirmware(savedFirmware); + + Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); + Assert.assertEquals(foundFirmware.getTitle(), savedFirmware.getTitle()); + + firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + } + + @Test(expected = DataValidationException.class) + public void testSaveFirmwareWithEmptyTenant() { + Firmware firmware = new Firmware(); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + firmwareService.saveFirmware(firmware); + } + + @Test(expected = DataValidationException.class) + public void testSaveFirmwareWithEmptyTitle() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + firmwareService.saveFirmware(firmware); + } + + @Test(expected = DataValidationException.class) + public void testSaveFirmwareWithEmptyFileName() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + firmwareService.saveFirmware(firmware); + } + + @Test(expected = DataValidationException.class) + public void testSaveFirmwareWithEmptyContentType() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setData(DATA); + firmwareService.saveFirmware(firmware); + } + + @Test(expected = DataValidationException.class) + public void testSaveFirmwareWithEmptyData() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmwareService.saveFirmware(firmware); + } + + @Test(expected = DataValidationException.class) + public void testSaveFirmwareWithInvalidTenant() { + Firmware firmware = new Firmware(); + firmware.setTenantId(new TenantId(Uuids.timeBased())); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + firmwareService.saveFirmware(firmware); + } + + + + @Test(expected = DataValidationException.class) + public void testDeleteFirmwareWithReferenceByDevice() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + + Device device = new Device(); + device.setTenantId(tenantId); + device.setName("My device"); + device.setType("default"); + device.setFirmwareId(savedFirmware.getId()); + Device savedDevice = deviceService.saveDevice(device); + + try { + firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + } finally { + deviceService.deleteDevice(tenantId, savedDevice.getId()); + firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + } + } + + @Test(expected = DataValidationException.class) + public void testDeleteFirmwareWithReferenceByDeviceProfile() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); + deviceProfile.setFirmwareId(savedFirmware.getId()); + DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); + + try { + firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + } finally { + deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId()); + firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + } + } + + @Test + public void testFindFirmwareById() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + + Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); + Assert.assertNotNull(foundFirmware); + Assert.assertEquals(savedFirmware, foundFirmware); + firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + } + + @Test + public void testDeleteFirmware() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + + Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); + Assert.assertNotNull(foundFirmware); + firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); + Assert.assertNull(foundFirmware); + } + + @Test + public void testFindTenantFirmwaresByTenantId() { + List firmwares = new ArrayList<>(); + for (int i = 0; i < 165; i++) { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setData(DATA); + firmwares.add(new FirmwareInfo(firmwareService.saveFirmware(firmware))); + } + + List loadedFirmwares = new ArrayList<>(); + PageLink pageLink = new PageLink(16); + PageData pageData; + do { + pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink); + loadedFirmwares.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(firmwares, idComparator); + Collections.sort(loadedFirmwares, idComparator); + + Assert.assertEquals(firmwares, loadedFirmwares); + + firmwareService.deleteFirmwaresByTenantId(tenantId); + + pageLink = new PageLink(31); + pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertTrue(pageData.getData().isEmpty()); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java new file mode 100644 index 0000000000..a89414e3a2 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/FirmwareServiceSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 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.dao.service.sql; + +import org.thingsboard.server.dao.service.BaseFirmwareServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class FirmwareServiceSqlTest extends BaseFirmwareServiceTest { +} diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index a548ceec30..8297553ea6 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -29,4 +29,5 @@ DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS api_usage_state; DROP TABLE IF EXISTS resource; +DROP TABLE IF EXISTS firmware; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index 333ce03fba..fbc0c76391 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -30,3 +30,4 @@ DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; DROP TABLE IF EXISTS api_usage_state; DROP TABLE IF EXISTS resource; +DROP TABLE IF EXISTS firmware; diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index 211cebb2ad..950d3b2722 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -28,4 +28,6 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS api_usage_state; \ No newline at end of file +DROP TABLE IF EXISTS api_usage_state; +DROP TABLE IF EXISTS resource; +DROP TABLE IF EXISTS firmware; \ No newline at end of file From 7954f91feed37aa6239ce75e47f0410432e0af24 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Apr 2021 11:45:29 +0300 Subject: [PATCH 011/127] added upgrade for firmware --- .../main/data/upgrade/3.2.2/schema_update.sql | 61 +++++++++++++++++++ .../DefaultSystemDataLoaderService.java | 8 --- .../service/install/InstallScripts.java | 27 -------- .../install/SqlDatabaseUpgradeService.java | 24 ++------ .../install/SystemDataLoaderService.java | 2 - .../main/resources/sql/schema-entities.sql | 16 ++--- 6 files changed, 74 insertions(+), 64 deletions(-) create mode 100644 application/src/main/data/upgrade/3.2.2/schema_update.sql diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql new file mode 100644 index 0000000000..450160b104 --- /dev/null +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -0,0 +1,61 @@ +-- +-- Copyright © 2016-2021 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. +-- + +CREATE TABLE IF NOT EXISTS resource ( + id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid NOT NULL, + title varchar(255) NOT NULL, + resource_type varchar(32) NOT NULL, + resource_key varchar(255) NOT NULL, + search_text varchar(255), + file_name varchar(255) NOT NULL, + data varchar, + CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key) +); + +CREATE TABLE IF NOT EXISTS firmware ( + id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid NOT NULL, + title varchar(255) NOT NULL, + search_text varchar(255), + file_name varchar(255) NOT NULL, + content_type varchar(255) NOT NULL, + data bytea +); + +ALTER TABLE device_profile + ADD COLUMN IF NOT EXISTS firmware_id uuid; + +ALTER TABLE device + ADD COLUMN IF NOT EXISTS firmware_id uuid; + +DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN + ALTER TABLE device_profile + ADD CONSTRAINT fk_firmware_device_profile + FOREIGN KEY (firmware_id) REFERENCES firmware(id); + END IF; + + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN + ALTER TABLE device + ADD CONSTRAINT fk_firmware_device + FOREIGN KEY (firmware_id) REFERENCES firmware(id); + END IF; + END; +$$; 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 6f257a367e..b49819b7ac 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 @@ -58,11 +58,8 @@ 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; @@ -445,11 +442,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { installScripts.loadSystemWidgets(); } - @Override - public void loadSystemLwm2mResources() throws Exception { - installScripts.loadSystemLwm2mResources(); - } - private User createUser(Authority authority, TenantId tenantId, CustomerId customerId, diff --git a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java index fb7634118b..f36a8f84ce 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java +++ b/application/src/main/java/org/thingsboard/server/service/install/InstallScripts.java @@ -22,8 +22,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.ResourceType; -import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -33,7 +31,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.dao.dashboard.DashboardService; -import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.resource.TbResourceService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -45,7 +42,6 @@ import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Base64; import java.util.Optional; import static org.thingsboard.server.service.install.DatabaseHelper.objectMapper; @@ -196,29 +192,6 @@ public class InstallScripts { } } - public void loadSystemLwm2mResources() throws Exception { - Path modelsDir = Paths.get(getDataDir(), MODELS_DIR); - if (Files.isDirectory(modelsDir)) { - try (DirectoryStream dirStream = Files.newDirectoryStream(modelsDir, path -> path.toString().endsWith(XML_EXT))) { - dirStream.forEach( - path -> { - try { - byte[] fileBytes = Files.readAllBytes(path); - TbResource resource = new TbResource(); - resource.setFileName(path.getFileName().toString()); - resource.setTenantId(TenantId.SYS_TENANT_ID); - resource.setResourceType(ResourceType.LWM2M_MODEL); - resource.setData(Base64.getEncoder().encodeToString(fileBytes)); - resourceService.saveResource(resource); - } catch (Exception e) { - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", path.toString())); - } - } - ); - } - } - } - public void loadDashboards(TenantId tenantId, CustomerId customerId) throws Exception { Path dashboardsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, DASHBOARDS_DIR); try (DirectoryStream dirStream = Files.newDirectoryStream(dashboardsDir, path -> path.toString().endsWith(JSON_EXT))) { 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 6b946bad52..922db4c2b7 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 @@ -449,26 +449,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService case "3.2.2": try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) { log.info("Updating schema ..."); - try { - conn.createStatement().execute("CREATE TABLE IF NOT EXISTS resource ( " + - "id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY, " + - "created_time bigint NOT NULL, " + - "tenant_id uuid NOT NULL, " + - "title varchar(255) NOT NULL, " + - "resource_type varchar(32) NOT NULL, " + - "resource_key varchar(255) NOT NULL, " + - "search_text varchar(255), " + - "file_name varchar(255) NOT NULL, " + - "data varchar, " + - "CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)" + - ");"); - - conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;"); - installScripts.loadSystemLwm2mResources(); - } catch (Exception e) { - log.error("Failed updating schema!!!", e); - } + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.2.2", SCHEMA_UPDATE_SQL); + loadSql(schemaUpdateFile, conn); + conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3003000;"); log.info("Schema updated."); + } catch (Exception e) { + log.error("Failed updating schema!!!", e); } break; default: diff --git a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index a6f33f476f..73e2b6ea57 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -33,6 +33,4 @@ public interface SystemDataLoaderService { void deleteSystemWidgetBundle(String bundleAlias) throws Exception; - void loadSystemLwm2mResources() throws Exception; - } diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 5d7fc750c4..431eaef9ff 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -176,14 +176,14 @@ CREATE TABLE IF NOT EXISTS rule_node_state ( ); CREATE TABLE IF NOT EXISTS firmware ( - id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, - created_time bigint NOT NULL, - tenant_id uuid NOT NULL, - title varchar(255) NOT NULL, - search_text varchar(255), - file_name varchar(255) NOT NULL, - content_type varchar(255) NOT NULL, - data bytea + id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid NOT NULL, + title varchar(255) NOT NULL, + search_text varchar(255), + file_name varchar(255) NOT NULL, + content_type varchar(255) NOT NULL, + data bytea ); CREATE TABLE IF NOT EXISTS device_profile ( From 935aab753506e2e5d46718ae458588ef1439e0a7 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 9 Apr 2021 15:32:06 +0300 Subject: [PATCH 012/127] added checksum and checksum algorithm for firmware --- .../main/data/upgrade/3.2.2/schema_update.sql | 11 +- .../server/controller/FirmwareController.java | 35 ++++- .../install/ThingsboardInstallService.java | 1 - .../server/dao/firmware/FirmwareService.java | 3 +- .../server/common/data/Firmware.java | 12 ++ .../server/common/data/FirmwareInfo.java | 8 +- .../dao/device/DeviceProfileServiceImpl.java | 14 ++ .../server/dao/device/DeviceServiceImpl.java | 15 ++ .../dao/firmware/BaseFirmwareService.java | 136 +++++++++++++++++- .../server/dao/model/ModelConstants.java | 5 + .../server/dao/model/sql/FirmwareEntity.java | 34 ++++- .../dao/model/sql/FirmwareInfoEntity.java | 26 ++-- .../resources/sql/schema-entities-hsql.sql | 11 +- .../main/resources/sql/schema-entities.sql | 11 +- .../server/dao/SqlDaoServiceTestSuite.java | 2 +- .../service/BaseDeviceProfileServiceTest.java | 70 ++++++--- .../dao/service/BaseDeviceServiceTest.java | 44 ++++++ .../dao/service/BaseFirmwareServiceTest.java | 130 ++++++++++++++++- 18 files changed, 511 insertions(+), 57 deletions(-) diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index 450160b104..d724ab6152 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -32,10 +32,15 @@ CREATE TABLE IF NOT EXISTS firmware ( created_time bigint NOT NULL, tenant_id uuid NOT NULL, title varchar(255) NOT NULL, + version varchar(255) NOT NULL, + file_name varchar(255), + content_type varchar(255), + checksum_algorithm varchar(32), + checksum varchar(1020), + data binary, + additional_info varchar, search_text varchar(255), - file_name varchar(255) NOT NULL, - content_type varchar(255) NOT NULL, - data bytea + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) ); ALTER TABLE device_profile diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index 7736abba11..4d53be8c07 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; +import org.thingsboard.server.service.security.permission.Resource; import java.nio.ByteBuffer; @@ -97,14 +98,36 @@ public class FirmwareController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/firmware", method = RequestMethod.POST) @ResponseBody - public Firmware saveFirmware(@RequestParam("title") String title, - @RequestBody MultipartFile firmwareFile) throws ThingsboardException { - checkParameter("title", title); + public FirmwareInfo saveFirmwareInfo(@RequestParam("title") FirmwareInfo firmwareInfo) throws ThingsboardException { + checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE); try { - checkNotNull(firmwareFile); - Firmware firmware = new Firmware(); + return firmwareService.saveFirmwareInfo(firmwareInfo); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST) + @ResponseBody + public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, + @RequestParam String checksum, + @RequestParam String checksumAlgorithm, + @RequestBody MultipartFile firmwareFile) throws ThingsboardException { + checkParameter(FIRMWARE_ID, strFirmwareId); + checkParameter("checksum", checksum); + try { + FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); + FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ); + + Firmware firmware = new Firmware(firmwareId); + firmware.setCreatedTime(info.getCreatedTime()); firmware.setTenantId(getTenantId()); - firmware.setTitle(title); + firmware.setVersion(info.getVersion()); + firmware.setAdditionalInfo(info.getAdditionalInfo()); + + firmware.setChecksumAlgorithm(checksumAlgorithm); + firmware.setChecksum(checksum); firmware.setFileName(firmwareFile.getOriginalFilename()); firmware.setContentType(firmwareFile.getContentType()); firmware.setData(ByteBuffer.wrap(firmwareFile.getBytes())); diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 4a9a110fab..a22a22d240 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -230,7 +230,6 @@ public class ThingsboardInstallService { systemDataLoaderService.createAdminSettings(); systemDataLoaderService.loadSystemWidgets(); systemDataLoaderService.createOAuth2Templates(); - systemDataLoaderService.loadSystemLwm2mResources(); // systemDataLoaderService.loadSystemPlugins(); // systemDataLoaderService.loadSystemRules(); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java index bff1a6f806..140cb14f1a 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java @@ -22,9 +22,10 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; - public interface FirmwareService { + FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo); + Firmware saveFirmware(Firmware firmware); Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java b/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java index b4155a578a..6b8f48c5e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java @@ -27,6 +27,14 @@ public class Firmware extends FirmwareInfo { private static final long serialVersionUID = 3091601761339422546L; + private String fileName; + + private String contentType; + + private String checksumAlgorithm; + + private String checksum; + private transient ByteBuffer data; public Firmware() { @@ -39,6 +47,10 @@ public class Firmware extends FirmwareInfo { public Firmware(Firmware firmware) { super(firmware); + this.fileName = firmware.getFileName(); + this.contentType = firmware.getContentType(); this.data = firmware.getData(); + this.checksumAlgorithm = firmware.getChecksumAlgorithm(); + this.checksum = firmware.getChecksum(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java index b950268649..d2fa921d19 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -24,14 +24,13 @@ import org.thingsboard.server.common.data.id.TenantId; @Slf4j @Data @EqualsAndHashCode(callSuper = true) -public class FirmwareInfo extends SearchTextBased implements HasTenantId { +public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo implements HasTenantId { private static final long serialVersionUID = 3168391583570815419L; private TenantId tenantId; private String title; - private String fileName; - private String contentType; + private String version; public FirmwareInfo() { super(); @@ -45,8 +44,7 @@ public class FirmwareInfo extends SearchTextBased implements HasTena super(firmwareInfo); this.tenantId = firmwareInfo.getTenantId(); this.title = firmwareInfo.getTitle(); - this.fileName = firmwareInfo.getFileName(); - this.contentType = firmwareInfo.getContentType(); + this.version = firmwareInfo.getVersion(); } @Override 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 bff523d956..9f85dd1cb5 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 @@ -40,6 +40,7 @@ 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.Firmware; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; @@ -59,6 +60,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; @@ -107,6 +109,9 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D @Autowired private CacheManager cacheManager; + @Autowired + private FirmwareService firmwareService; + private final Lock findOrCreateLock = new ReentrantLock(); @Cacheable(cacheNames = DEVICE_PROFILE_CACHE, key = "{#deviceProfileId.id}") @@ -389,6 +394,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D } } + if (deviceProfile.getFirmwareId() != null) { + Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId()); + if (firmware == null) { + throw new DataValidationException("Can't assign non-existent firmware!"); + } + if (firmware.getData() == null) { + throw new DataValidationException("Can't assign firmware with empty data!"); + } + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 7ce2e650d2..d1d76b1e95 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; @@ -68,6 +69,7 @@ import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @@ -128,6 +130,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Lazy private TbTenantProfileCache tenantProfileCache; + @Autowired + private FirmwareService firmwareService; + @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceInfoById [{}]", deviceId); @@ -598,6 +603,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe throw new DataValidationException("Can't assign device to customer from different tenant!"); } } + + if (device.getFirmwareId() != null) { + Firmware firmware = firmwareService.findFirmwareById(tenantId, device.getFirmwareId()); + if (firmware == null) { + throw new DataValidationException("Can't assign non-existent firmware!"); + } + if (firmware.getData() == null) { + throw new DataValidationException("Can't assign firmware with empty data!"); + } + } } }; diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index bf1c4f10e6..35d4a3c39c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.dao.firmware; +import com.google.common.hash.HashFunction; +import com.google.common.hash.Hashing; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -53,11 +55,36 @@ public class BaseFirmwareService implements FirmwareService { this.firmwareInfoDao = firmwareInfoDao; } + @Override + public FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo) { + log.trace("Executing saveFirmwareInfo [{}]", firmwareInfo); + firmwareInfoValidator.validate(firmwareInfo, FirmwareInfo::getTenantId); + try { + return firmwareInfoDao.save(firmwareInfo.getTenantId(), firmwareInfo); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("firmware_tenant_title_version_unq_key")) { + throw new DataValidationException("Firmware with such title and version already exists!"); + } else { + throw t; + } + } + } + @Override public Firmware saveFirmware(Firmware firmware) { log.trace("Executing saveFirmware [{}]", firmware); - firmwareValidator.validate(firmware, Firmware::getTenantId); - return firmwareDao.save(firmware.getTenantId(), firmware); + firmwareValidator.validate(firmware, FirmwareInfo::getTenantId); + try { + return firmwareDao.save(firmware.getTenantId(), firmware); + } catch (Exception t) { + ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); + if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("firmware_tenant_title_version_unq_key")) { + throw new DataValidationException("Firmware with such title and version already exists!"); + } else { + throw t; + } + } } @Override @@ -107,6 +134,42 @@ public class BaseFirmwareService implements FirmwareService { tenantFirmwareRemover.removeEntities(tenantId, tenantId); } + private DataValidator firmwareInfoValidator = new DataValidator<>() { + + @Override + protected void validateDataImpl(TenantId tenantId, FirmwareInfo firmware) { + if (firmware.getTenantId() == null) { + throw new DataValidationException("Firmware should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Firmware is referencing to non-existent tenant!"); + } + } + + if (StringUtils.isEmpty(firmware.getTitle())) { + throw new DataValidationException("Firmware title should be specified!"); + } + + if (StringUtils.isEmpty(firmware.getVersion())) { + throw new DataValidationException("Firmware version should be specified!"); + } + } + + @Override + protected void validateUpdate(TenantId tenantId, FirmwareInfo firmware) { + FirmwareInfo firmwareOld = firmwareInfoDao.findById(tenantId, firmware.getUuidId()); + + if (!firmwareOld.getTitle().equals(firmware.getTitle())) { + throw new DataValidationException("Updating firmware title is prohibited!"); + } + + if (!firmwareOld.getVersion().equals(firmware.getVersion())) { + throw new DataValidationException("Updating firmware version is prohibited!"); + } + } + }; + private DataValidator firmwareValidator = new DataValidator<>() { @Override @@ -123,22 +186,91 @@ public class BaseFirmwareService implements FirmwareService { if (StringUtils.isEmpty(firmware.getTitle())) { throw new DataValidationException("Firmware title should be specified!"); } + + if (StringUtils.isEmpty(firmware.getVersion())) { + throw new DataValidationException("Firmware version should be specified!"); + } + if (StringUtils.isEmpty(firmware.getFileName())) { throw new DataValidationException("Firmware file name should be specified!"); } + if (StringUtils.isEmpty(firmware.getContentType())) { throw new DataValidationException("Firmware content type should be specified!"); } + + if (StringUtils.isEmpty(firmware.getChecksum())) { + throw new DataValidationException("Firmware checksum should be specified!"); + } + ByteBuffer data = firmware.getData(); if (data == null || !data.hasArray() || data.array().length == 0) { throw new DataValidationException("Firmware data should be specified!"); } + + if (firmware.getChecksumAlgorithm() != null) { + HashFunction hashFunction; + switch (firmware.getChecksumAlgorithm()) { + case "sha256": + hashFunction = Hashing.sha256(); + break; + case "md5": + hashFunction = Hashing.md5(); + break; + case "crc32": + hashFunction = Hashing.crc32(); + break; + default: + throw new DataValidationException("Unknown checksum algorithm!"); + } + + String currentChecksum = hashFunction.hashBytes(data.array()).toString(); + ; + + if (!currentChecksum.equals(firmware.getChecksum())) { + throw new DataValidationException("Wrong firmware file!"); + } + } + } + + @Override + protected void validateUpdate(TenantId tenantId, Firmware firmware) { + Firmware firmwareOld = firmwareDao.findById(tenantId, firmware.getUuidId()); + + if (!firmwareOld.getTitle().equals(firmware.getTitle())) { + throw new DataValidationException("Updating firmware title is prohibited!"); + } + + if (!firmwareOld.getVersion().equals(firmware.getVersion())) { + throw new DataValidationException("Updating firmware version is prohibited!"); + } + + if (firmwareOld.getFileName() != null && !firmwareOld.getFileName().equals(firmware.getFileName())) { + throw new DataValidationException("Updating firmware file name is prohibited!"); + } + + if (firmwareOld.getContentType() != null && !firmwareOld.getContentType().equals(firmware.getContentType())) { + throw new DataValidationException("Updating firmware content type is prohibited!"); + } + + if (firmwareOld.getChecksumAlgorithm() != null && !firmwareOld.getChecksumAlgorithm().equals(firmware.getChecksumAlgorithm())) { + throw new DataValidationException("Updating firmware content type is prohibited!"); + } + + if (firmwareOld.getChecksum() != null && !firmwareOld.getChecksum().equals(firmware.getChecksum())) { + throw new DataValidationException("Updating firmware content type is prohibited!"); + } + + if (firmwareOld.getData() != null && !firmwareOld.getData().equals(firmware.getData())) { + throw new DataValidationException("Updating firmware data is prohibited!"); + } } }; private PaginatedRemover tenantFirmwareRemover = new PaginatedRemover<>() { + @Override protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return firmwareInfoDao.findFirmwareInfoByTenantId(id, pageLink); 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 05af88eeb9..67a4ec9f77 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 @@ -475,9 +475,14 @@ public class ModelConstants { public static final String FIRMWARE_TABLE_NAME = "firmware"; public static final String FIRMWARE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; public static final String FIRMWARE_TITLE_COLUMN = TITLE_PROPERTY; + public static final String FIRMWARE_VERSION_COLUMN = "version"; public static final String FIRMWARE_FILE_NAME_COLUMN = "file_name"; public static final String FIRMWARE_CONTENT_TYPE_COLUMN = "content_type"; + public static final String FIRMWARE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm"; + public static final String FIRMWARE_CHECKSUM_COLUMN = "checksum"; public static final String FIRMWARE_DATA_COLUMN = "data"; + public static final String FIRMWARE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY; + /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java index 1823c5eae5..20d1115c52 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -15,13 +15,18 @@ */ package org.thingsboard.server.dao.model.sql; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; @@ -29,17 +34,21 @@ import javax.persistence.Table; import java.nio.ByteBuffer; import java.util.UUID; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ALGORITHM_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @Data @EqualsAndHashCode(callSuper = true) @Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = FIRMWARE_TABLE_NAME) public class FirmwareEntity extends BaseSqlEntity implements SearchTextEntity { @@ -49,15 +58,28 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex @Column(name = FIRMWARE_TITLE_COLUMN) private String title; + @Column(name = FIRMWARE_VERSION_COLUMN) + private String version; + @Column(name = FIRMWARE_FILE_NAME_COLUMN) private String fileName; @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) private String contentType; + @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN) + private String checksumAlgorithm; + + @Column(name = FIRMWARE_CHECKSUM_COLUMN) + private String checksum; + @Column(name = FIRMWARE_DATA_COLUMN, columnDefinition = "BINARY") private byte[] data; + @Type(type = "json") + @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN) + private JsonNode additionalInfo; + @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; @@ -70,9 +92,13 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); this.title = firmware.getTitle(); + this.version = firmware.getVersion(); this.fileName = firmware.getFileName(); this.contentType = firmware.getContentType(); + this.checksumAlgorithm = firmware.getChecksumAlgorithm(); + this.checksum = firmware.getChecksum(); this.data = firmware.getData().array(); + this.additionalInfo = firmware.getAdditionalInfo(); } @Override @@ -91,9 +117,15 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); firmware.setTitle(title); + firmware.setVersion(version); firmware.setFileName(fileName); firmware.setContentType(contentType); - firmware.setData(ByteBuffer.wrap(data)); + firmware.setChecksumAlgorithm(checksumAlgorithm); + firmware.setChecksum(checksum); + if (data != null) { + firmware.setData(ByteBuffer.wrap(data)); + } + firmware.setAdditionalInfo(additionalInfo); return firmware; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java index ec7ee7206f..f0c19c38c8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java @@ -15,29 +15,34 @@ */ package org.thingsboard.server.dao.model.sql; +import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; import java.util.UUID; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; -import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @Data @EqualsAndHashCode(callSuper = true) @Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) @Table(name = FIRMWARE_TABLE_NAME) public class FirmwareInfoEntity extends BaseSqlEntity implements SearchTextEntity { @@ -47,11 +52,12 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Column(name = FIRMWARE_TITLE_COLUMN) private String title; - @Column(name = FIRMWARE_FILE_NAME_COLUMN) - private String fileName; + @Column(name = FIRMWARE_VERSION_COLUMN) + private String version; - @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) - private String contentType; + @Type(type = "json") + @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN) + private JsonNode additionalInfo; @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; @@ -65,8 +71,8 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); this.title = firmware.getTitle(); - this.fileName = firmware.getFileName(); - this.contentType = firmware.getContentType(); + this.version = firmware.getVersion(); + this.additionalInfo = firmware.getAdditionalInfo(); } @Override @@ -85,8 +91,8 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); firmware.setTitle(title); - firmware.setFileName(fileName); - firmware.setContentType(contentType); + firmware.setVersion(version); + firmware.setAdditionalInfo(additionalInfo); return firmware; } } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 2bac2f9b99..faaf91a94f 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -162,10 +162,15 @@ CREATE TABLE IF NOT EXISTS firmware ( created_time bigint NOT NULL, tenant_id uuid NOT NULL, title varchar(255) NOT NULL, + version varchar(255) NOT NULL, + file_name varchar(255), + content_type varchar(255), + checksum_algorithm varchar(32), + checksum varchar(1020), + data binary, + additional_info varchar, search_text varchar(255), - file_name varchar(255) NOT NULL, - content_type varchar(255) NOT NULL, - data binary + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) ); CREATE TABLE IF NOT EXISTS device_profile ( diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 431eaef9ff..cf3ef0f6a6 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -180,10 +180,15 @@ CREATE TABLE IF NOT EXISTS firmware ( created_time bigint NOT NULL, tenant_id uuid NOT NULL, title varchar(255) NOT NULL, + version varchar(255) NOT NULL, + file_name varchar(255), + content_type varchar(255), + checksum_algorithm varchar(32), + checksum varchar(1020), + data bytea, + additional_info varchar, search_text varchar(255), - file_name varchar(255) NOT NULL, - content_type varchar(255) NOT NULL, - data bytea + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) ); CREATE TABLE IF NOT EXISTS device_profile ( diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index 0d9d8689fe..0d0f7f056b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.sql.FirmwareServiceSqlTest" + "org.thingsboard.server.dao.service.sql.*Test" }) public class SqlDaoServiceTestSuite { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java index 0fd8925fc9..e377baa2cb 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java @@ -27,19 +27,19 @@ import org.junit.Test; 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.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.DataValidationException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; @@ -82,18 +82,49 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); } + @Test + public void testSaveDeviceProfileWithFirmware() { + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); + DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); + Assert.assertNotNull(savedDeviceProfile); + Assert.assertNotNull(savedDeviceProfile.getId()); + Assert.assertTrue(savedDeviceProfile.getCreatedTime() > 0); + Assert.assertEquals(deviceProfile.getName(), savedDeviceProfile.getName()); + Assert.assertEquals(deviceProfile.getDescription(), savedDeviceProfile.getDescription()); + Assert.assertEquals(deviceProfile.getProfileData(), savedDeviceProfile.getProfileData()); + Assert.assertEquals(deviceProfile.isDefault(), savedDeviceProfile.isDefault()); + Assert.assertEquals(deviceProfile.getDefaultRuleChainId(), savedDeviceProfile.getDefaultRuleChainId()); + + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle("my firmware"); + firmware.setVersion("v1.0"); + firmware.setFileName("test.txt"); + firmware.setContentType("text/plain"); + firmware.setChecksumAlgorithm("sha256"); + firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); + firmware.setData(ByteBuffer.wrap(new byte[]{1})); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + + deviceProfile.setFirmwareId(savedFirmware.getId()); + + deviceProfileService.saveDeviceProfile(savedDeviceProfile); + DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId()); + Assert.assertEquals(savedDeviceProfile.getName(), foundDeviceProfile.getName()); + } + @Test public void testFindDeviceProfileById() { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId()); Assert.assertNotNull(foundDeviceProfile); Assert.assertEquals(savedDeviceProfile, foundDeviceProfile); - } + } @Test public void testFindDeviceProfileInfoById() { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); DeviceProfileInfo foundDeviceProfileInfo = deviceProfileService.findDeviceProfileInfoById(tenantId, savedDeviceProfile.getId()); Assert.assertNotNull(foundDeviceProfileInfo); @@ -124,7 +155,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { ListeningExecutorService testExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(100)); try { List> futures = new ArrayList<>(); - for (int i = 0; i < 50; i ++) { + for (int i = 0; i < 50; i++) { futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 1"))); futures.add(testExecutor.submit(() -> deviceProfileService.findOrCreateDeviceProfile(tenantId, "Device Profile 2"))); } @@ -138,8 +169,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @Test public void testSetDefaultDeviceProfile() { - DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId,"Device Profile 1"); - DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile 2"); + DeviceProfile deviceProfile1 = this.createDeviceProfile(tenantId, "Device Profile 1"); + DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId, "Device Profile 2"); DeviceProfile savedDeviceProfile1 = deviceProfileService.saveDeviceProfile(deviceProfile1); DeviceProfile savedDeviceProfile2 = deviceProfileService.saveDeviceProfile(deviceProfile2); @@ -165,16 +196,16 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testSaveDeviceProfileWithSameName() { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); deviceProfileService.saveDeviceProfile(deviceProfile); - DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId,"Device Profile"); + DeviceProfile deviceProfile2 = this.createDeviceProfile(tenantId, "Device Profile"); deviceProfileService.saveDeviceProfile(deviceProfile2); } @Ignore @Test(expected = DataValidationException.class) public void testChangeDeviceProfileTypeWithExistingDevices() { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); Device device = new Device(); device.setTenantId(tenantId); @@ -189,7 +220,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testChangeDeviceProfileTransportTypeWithExistingDevices() { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); Device device = new Device(); device.setTenantId(tenantId); @@ -203,7 +234,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @Test(expected = DataValidationException.class) public void testDeleteDeviceProfileWithExistingDevice() { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); Device device = new Device(); device.setTenantId(tenantId); @@ -216,7 +247,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { @Test public void testDeleteDeviceProfile() { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"); + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId()); DeviceProfile foundDeviceProfile = deviceProfileService.findDeviceProfileById(tenantId, savedDeviceProfile.getId()); @@ -233,8 +264,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { Assert.assertEquals(1, pageData.getTotalElements()); deviceProfiles.addAll(pageData.getData()); - for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"+i); + for (int i = 0; i < 28; i++) { + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile" + i); deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile)); } @@ -275,8 +306,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { Assert.assertEquals(1, deviceProfilePageData.getTotalElements()); deviceProfiles.addAll(deviceProfilePageData.getData()); - for (int i=0;i<28;i++) { - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId,"Device Profile"+i); + for (int i = 0; i < 28; i++) { + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile" + i); deviceProfiles.add(deviceProfileService.saveDeviceProfile(deviceProfile)); } @@ -297,7 +328,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { List deviceProfileInfos = deviceProfiles.stream() .map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(), - deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); + deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos); @@ -312,4 +343,5 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(1, pageData.getTotalElements()); } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index b323d0088c..d9ca69632b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.dao.exception.DataValidationException; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -88,6 +89,49 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { deviceService.deleteDevice(tenantId, savedDevice.getId()); } + + @Test + public void testSaveDeviceWithFirmware() { + Device device = new Device(); + device.setTenantId(tenantId); + device.setName("My device"); + device.setType("default"); + Device savedDevice = deviceService.saveDevice(device); + + Assert.assertNotNull(savedDevice); + Assert.assertNotNull(savedDevice.getId()); + Assert.assertTrue(savedDevice.getCreatedTime() > 0); + Assert.assertEquals(device.getTenantId(), savedDevice.getTenantId()); + Assert.assertNotNull(savedDevice.getCustomerId()); + Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId()); + Assert.assertEquals(device.getName(), savedDevice.getName()); + + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); + Assert.assertNotNull(deviceCredentials); + Assert.assertNotNull(deviceCredentials.getId()); + Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); + Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType()); + Assert.assertNotNull(deviceCredentials.getCredentialsId()); + Assert.assertEquals(20, deviceCredentials.getCredentialsId().length()); + + + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle("my firmware"); + firmware.setVersion("v1.0"); + firmware.setFileName("test.txt"); + firmware.setContentType("text/plain"); + firmware.setChecksumAlgorithm("sha256"); + firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); + firmware.setData(ByteBuffer.wrap(new byte[]{1})); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + + savedDevice.setFirmwareId(savedFirmware.getId()); + + deviceService.saveDevice(savedDevice); + Device foundDevice = deviceService.findDeviceById(tenantId, savedDevice.getId()); + Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); + } @Test(expected = DataValidationException.class) public void testSaveDeviceWithEmptyName() { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java index b5383a1809..3abb97875b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java @@ -20,6 +20,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Firmware; @@ -39,8 +40,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public static final String TITLE = "My firmware"; private static final String FILE_NAME = "filename.txt"; + private static final String VERSION = "v1.0"; private static final String CONTENT_TYPE = "text/plain"; - private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{0}); + private static final String CHECKSUM_ALGORITHM = "sha256"; + private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1}); private IdComparator idComparator = new IdComparator<>(); @@ -65,8 +69,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); Firmware savedFirmware = firmwareService.saveFirmware(firmware); @@ -79,7 +86,7 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Assert.assertEquals(firmware.getContentType(), savedFirmware.getContentType()); Assert.assertEquals(firmware.getData(), savedFirmware.getData()); - savedFirmware.setTitle("My new firmware"); + savedFirmware.setAdditionalInfo(JacksonUtil.newObjectNode()); firmwareService.saveFirmware(savedFirmware); Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, savedFirmware.getId()); @@ -88,12 +95,52 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); } + @Test + public void testSaveFirmwareInfoAndUpdateWithData() { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTenantId(tenantId); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION); + FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo); + + Assert.assertNotNull(savedFirmwareInfo); + Assert.assertNotNull(savedFirmwareInfo.getId()); + Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0); + Assert.assertEquals(firmwareInfo.getTenantId(), savedFirmwareInfo.getTenantId()); + Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); + + Firmware firmware = new Firmware(savedFirmwareInfo.getId()); + firmware.setCreatedTime(firmwareInfo.getCreatedTime()); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); + firmware.setData(DATA); + + firmwareService.saveFirmware(firmware); + + savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); + firmwareService.saveFirmwareInfo(savedFirmwareInfo); + + Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, firmware.getId()); + firmware.setAdditionalInfo(JacksonUtil.newObjectNode()); + Assert.assertEquals(foundFirmware.getTitle(), firmware.getTitle()); + + firmwareService.deleteFirmware(tenantId, savedFirmwareInfo.getId()); + } + @Test(expected = DataValidationException.class) public void testSaveFirmwareWithEmptyTenant() { Firmware firmware = new Firmware(); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); firmwareService.saveFirmware(firmware); } @@ -102,8 +149,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public void testSaveFirmwareWithEmptyTitle() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); firmwareService.saveFirmware(firmware); } @@ -113,7 +163,10 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); firmwareService.saveFirmware(firmware); } @@ -123,7 +176,10 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); firmwareService.saveFirmware(firmware); } @@ -133,8 +189,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmwareService.saveFirmware(firmware); } @@ -143,21 +202,76 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(new TenantId(Uuids.timeBased())); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); firmwareService.saveFirmware(firmware); } + @Test(expected = DataValidationException.class) + public void testSaveFirmwareWithEmptyChecksum() { + Firmware firmware = new Firmware(); + firmware.setTenantId(new TenantId(Uuids.timeBased())); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setData(DATA); + firmwareService.saveFirmware(firmware); + } + @Test(expected = DataValidationException.class) + public void testSaveFirmwareInfoWithExistingTitleAndVersion() { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTenantId(tenantId); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION); + firmwareService.saveFirmwareInfo(firmwareInfo); + + FirmwareInfo newFirmwareInfo = new FirmwareInfo(); + newFirmwareInfo.setTenantId(tenantId); + newFirmwareInfo.setTitle(TITLE); + newFirmwareInfo.setVersion(VERSION); + firmwareService.saveFirmwareInfo(newFirmwareInfo); + } + + @Test(expected = DataValidationException.class) + public void testSaveFirmwareWithExistingTitleAndVersion() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); + firmware.setData(DATA); + firmwareService.saveFirmware(firmware); + + Firmware newFirmware = new Firmware(); + newFirmware.setTenantId(tenantId); + newFirmware.setTitle(TITLE); + newFirmware.setVersion(VERSION); + newFirmware.setFileName(FILE_NAME); + newFirmware.setContentType(CONTENT_TYPE); + newFirmware.setData(DATA); + firmwareService.saveFirmware(newFirmware); + } @Test(expected = DataValidationException.class) public void testDeleteFirmwareWithReferenceByDevice() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); Firmware savedFirmware = firmwareService.saveFirmware(firmware); @@ -181,8 +295,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); Firmware savedFirmware = firmwareService.saveFirmware(firmware); @@ -203,8 +320,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); Firmware savedFirmware = firmwareService.saveFirmware(firmware); @@ -219,8 +339,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); Firmware savedFirmware = firmwareService.saveFirmware(firmware); @@ -238,8 +361,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); firmware.setTitle(TITLE); + firmware.setVersion(VERSION + i); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); firmware.setData(DATA); firmwares.add(new FirmwareInfo(firmwareService.saveFirmware(firmware))); } From 95c9562418a98bc60bdfeae89de9c29af3623bba Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 9 Apr 2021 15:35:07 +0300 Subject: [PATCH 013/127] refactored --- .../java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java index 0d0f7f056b..4f26e4eb4e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/SqlDaoServiceTestSuite.java @@ -24,7 +24,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClassnameFilters({ - "org.thingsboard.server.dao.service.sql.*Test" + "org.thingsboard.server.dao.service.sql.*SqlTest" }) public class SqlDaoServiceTestSuite { From 8860eef1bab4520824918c4dddc9686d57f0fdea Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 12 Apr 2021 13:35:02 +0300 Subject: [PATCH 014/127] added hasData for firmware --- .../main/data/upgrade/3.2.2/schema_update.sql | 2 +- .../server/controller/BaseController.java | 2 +- .../server/controller/FirmwareController.java | 25 ++++++++-- .../server/dao/firmware/FirmwareService.java | 2 + .../server/common/data/FirmwareInfo.java | 1 + .../common/data/id/EntityIdFactory.java | 2 + .../dao/firmware/BaseFirmwareService.java | 7 +++ .../server/dao/firmware/FirmwareInfoDao.java | 2 + .../server/dao/model/ModelConstants.java | 1 + .../server/dao/model/sql/FirmwareEntity.java | 1 + .../dao/model/sql/FirmwareInfoEntity.java | 18 +++++++ .../sql/firmware/FirmwareInfoRepository.java | 22 ++++++-- .../dao/sql/firmware/JpaFirmwareInfoDao.java | 25 ++++++++++ .../dao/service/BaseFirmwareServiceTest.java | 50 ++++++++++++++++++- 14 files changed, 149 insertions(+), 11 deletions(-) diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index d724ab6152..e68392230a 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -37,7 +37,7 @@ CREATE TABLE IF NOT EXISTS firmware ( content_type varchar(255), checksum_algorithm varchar(32), checksum varchar(1020), - data binary, + data bytea, additional_info varchar, search_text varchar(255), CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 4734361770..60c7e51aa4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -717,7 +717,7 @@ public abstract class BaseController { validateId(firmwareId, "Incorrect firmwareId " + firmwareId); Firmware firmware = firmwareService.findFirmwareById(getCurrentUser().getTenantId(), firmwareId); checkNotNull(firmware); - accessControlService.checkPermission(getCurrentUser(), Resource.TB_RESOURCE, operation, firmwareId, firmware); + accessControlService.checkPermission(getCurrentUser(), Resource.FIRMWARE, operation, firmwareId, firmware); return firmware; } catch (Exception e) { throw handleException(e, false); diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index 4d53be8c07..b844f2ece2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -98,7 +98,8 @@ public class FirmwareController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/firmware", method = RequestMethod.POST) @ResponseBody - public FirmwareInfo saveFirmwareInfo(@RequestParam("title") FirmwareInfo firmwareInfo) throws ThingsboardException { + public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException { + firmwareInfo.setTenantId(getTenantId()); checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE); try { return firmwareService.saveFirmwareInfo(firmwareInfo); @@ -112,7 +113,7 @@ public class FirmwareController extends BaseController { @ResponseBody public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, @RequestParam String checksum, - @RequestParam String checksumAlgorithm, + @RequestParam(required = false) String checksumAlgorithm, @RequestBody MultipartFile firmwareFile) throws ThingsboardException { checkParameter(FIRMWARE_ID, strFirmwareId); checkParameter("checksum", checksum); @@ -123,6 +124,7 @@ public class FirmwareController extends BaseController { Firmware firmware = new Firmware(firmwareId); firmware.setCreatedTime(info.getCreatedTime()); firmware.setTenantId(getTenantId()); + firmware.setTitle(info.getTitle()); firmware.setVersion(info.getVersion()); firmware.setAdditionalInfo(info.getAdditionalInfo()); @@ -138,7 +140,7 @@ public class FirmwareController extends BaseController { } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/firmware", method = RequestMethod.GET) + @RequestMapping(value = "/firmwares", method = RequestMethod.GET) @ResponseBody public PageData getFirmwares(@RequestParam int pageSize, @RequestParam int page, @@ -153,6 +155,23 @@ public class FirmwareController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET) + @ResponseBody + public PageData getFirmwares(@PathVariable("hasData") boolean hasData, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + try { + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndHasData(getTenantId(), hasData, pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE) @ResponseBody diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java index 140cb14f1a..0fd26fee6e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java @@ -34,6 +34,8 @@ public interface FirmwareService { PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink); + PageData findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink); + void deleteFirmware(TenantId tenantId, FirmwareId firmwareId); void deleteFirmwaresByTenantId(TenantId tenantId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java index d2fa921d19..bba9583d18 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -31,6 +31,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo private TenantId tenantId; private String title; private String version; + private boolean hasData; public FirmwareInfo() { super(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index 8e52a5b2a7..9179984a12 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -70,6 +70,8 @@ public class EntityIdFactory { return new ApiUsageStateId(uuid); case TB_RESOURCE: return new TbResourceId(uuid); + case FIRMWARE: + return new FirmwareId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 35d4a3c39c..9f484c8964 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -109,6 +109,13 @@ public class BaseFirmwareService implements FirmwareService { return firmwareInfoDao.findFirmwareInfoByTenantId(tenantId, pageLink); } + @Override + public PageData findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) { + log.trace("Executing findTenantFirmwaresByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + return firmwareInfoDao.findFirmwareInfoByTenantIdAndHasData(tenantId, hasData, pageLink); } + @Override public void deleteFirmware(TenantId tenantId, FirmwareId firmwareId) { log.trace("Executing deleteFirmware [{}]", firmwareId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java index cf8259c13c..a2af06ad9a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java @@ -27,4 +27,6 @@ public interface FirmwareInfoDao extends Dao { PageData findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink); + PageData findFirmwareInfoByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink); + } 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 67a4ec9f77..0fb748c83a 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 @@ -482,6 +482,7 @@ public class ModelConstants { public static final String FIRMWARE_CHECKSUM_COLUMN = "checksum"; public static final String FIRMWARE_DATA_COLUMN = "data"; public static final String FIRMWARE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY; + public static final String FIRMWARE_HAS_DATA_PROPERTY = "has_data"; /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java index 20d1115c52..2da0b110cc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -124,6 +124,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex firmware.setChecksum(checksum); if (data != null) { firmware.setData(ByteBuffer.wrap(data)); + firmware.setHasData(true); } firmware.setAdditionalInfo(additionalInfo); return firmware; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java index f0c19c38c8..ea0bd7ffe0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java @@ -20,6 +20,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,8 +32,10 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Table; +import javax.persistence.Transient; import java.util.UUID; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_HAS_DATA_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; @@ -62,6 +65,10 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; +// @Column(name = FIRMWARE_HAS_DATA_PROPERTY, insertable = false, updatable = false) + @Transient + private boolean hasData; + public FirmwareInfoEntity() { super(); } @@ -75,6 +82,16 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.additionalInfo = firmware.getAdditionalInfo(); } + public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version, Object additionalInfo, boolean hasData) { + this.id = id; + this.createdTime = createdTime; + this.tenantId = tenantId; + this.title = title; + this.version = version; + this.hasData = hasData; + this.additionalInfo = JacksonUtil.convertValue(additionalInfo, JsonNode.class); + } + @Override public String getSearchTextSource() { return title; @@ -93,6 +110,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S firmware.setTitle(title); firmware.setVersion(version); firmware.setAdditionalInfo(additionalInfo); + firmware.setHasData(hasData); return firmware; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java index dd4a53fe71..8dec49dca9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java @@ -25,10 +25,22 @@ import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity; import java.util.UUID; public interface FirmwareInfoRepository extends CrudRepository { - @Query("SELECT fi FROM FirmwareInfoEntity fi WHERE " + - "fi.tenantId = :tenantId " + - "AND LOWER(fi.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + + "f.tenantId = :tenantId " + + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") Page findAllByTenantId(@Param("tenantId") UUID tenantId, - @Param("searchText") String searchText, - Pageable pageable); + @Param("searchText") String searchText, + Pageable pageable); + + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + + "f.tenantId = :tenantId " + + "AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" + + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") + Page findAllByTenantIdAndHasData(@Param("tenantId") UUID tenantId, + @Param("hasData") boolean hasData, + @Param("searchText") String searchText, + Pageable pageable); + + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE f.id = :id") + FirmwareInfoEntity findFirmwareInfoById(@Param("id") UUID id); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java index ae61c9ac4f..3b80c1c7e5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java @@ -48,6 +48,21 @@ public class JpaFirmwareInfoDao extends JpaAbstractSearchTextDao findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink) { return DaoUtil.toPageData(firmwareInfoRepository @@ -56,4 +71,14 @@ public class JpaFirmwareInfoDao extends JpaAbstractSearchTextDao findFirmwareInfoByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) { + return DaoUtil.toPageData(firmwareInfoRepository + .findAllByTenantIdAndHasData( + tenantId.getId(), + hasData, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java index 3abb97875b..c4f80c551a 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java @@ -127,7 +127,9 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware foundFirmware = firmwareService.findFirmwareById(tenantId, firmware.getId()); firmware.setAdditionalInfo(JacksonUtil.newObjectNode()); + Assert.assertEquals(foundFirmware.getTitle(), firmware.getTitle()); + Assert.assertTrue(foundFirmware.isHasData()); firmwareService.deleteFirmware(tenantId, savedFirmwareInfo.getId()); } @@ -367,7 +369,10 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); - firmwares.add(new FirmwareInfo(firmwareService.saveFirmware(firmware))); + + FirmwareInfo info = new FirmwareInfo(firmwareService.saveFirmware(firmware)); + info.setHasData(true); + firmwares.add(info); } List loadedFirmwares = new ArrayList<>(); @@ -394,4 +399,47 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Assert.assertTrue(pageData.getData().isEmpty()); } + @Test + public void testFindTenantFirmwaresByTenantIdAndHasData() { + List firmwares = new ArrayList<>(); + for (int i = 0; i < 165; i++) { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION + i); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); + firmware.setData(DATA); + + FirmwareInfo info = new FirmwareInfo(firmwareService.saveFirmware(firmware)); + info.setHasData(true); + firmwares.add(info); + } + + List loadedFirmwares = new ArrayList<>(); + PageLink pageLink = new PageLink(16); + PageData pageData; + do { + pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, true, pageLink); + loadedFirmwares.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(firmwares, idComparator); + Collections.sort(loadedFirmwares, idComparator); + + Assert.assertEquals(firmwares, loadedFirmwares); + + firmwareService.deleteFirmwaresByTenantId(tenantId); + + pageLink = new PageLink(31); + pageData = firmwareService.findTenantFirmwaresByTenantId(tenantId, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertTrue(pageData.getData().isEmpty()); + } + } From 45fc32ce3e946d1d5d586957c126ab368334256b Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 12 Apr 2021 16:56:00 +0300 Subject: [PATCH 015/127] UI: Added firmware configuration --- .../server/controller/FirmwareController.java | 13 +- .../dao/firmware/BaseFirmwareService.java | 9 +- ui-ngx/src/app/core/http/firmware.service.ts | 123 ++++++++++++++++++ ui-ngx/src/app/core/http/http-utils.ts | 8 ++ ui-ngx/src/app/core/services/menu.service.ts | 12 ++ ui-ngx/src/app/core/utils.ts | 2 +- .../entity/entities-table-config.models.ts | 2 +- .../pages/firmware/firmware-routing.module.ts | 48 +++++++ .../firmware/firmware-table-config.resolve.ts | 99 ++++++++++++++ .../home/pages/firmware/firmware.module.ts | 35 +++++ .../pages/firmware/firmwares.component.html | 87 +++++++++++++ .../pages/firmware/firmwares.component.ts | 112 ++++++++++++++++ .../modules/home/pages/home-pages.module.ts | 2 + .../shared/components/file-input.component.ts | 55 +++++--- .../app/shared/models/entity-type.models.ts | 19 ++- .../src/app/shared/models/firmware.models.ts | 50 +++++++ .../src/app/shared/models/id/firmware-id.ts | 26 ++++ .../assets/locale/locale.constant-en_US.json | 27 ++++ 18 files changed, 697 insertions(+), 32 deletions(-) create mode 100644 ui-ngx/src/app/core/http/firmware.service.ts create mode 100644 ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts create mode 100644 ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts create mode 100644 ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html create mode 100644 ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts create mode 100644 ui-ngx/src/app/shared/models/firmware.models.ts create mode 100644 ui-ngx/src/app/shared/models/id/firmware-id.ts diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index b844f2ece2..f9be6dad82 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -112,11 +112,10 @@ public class FirmwareController extends BaseController { @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.POST) @ResponseBody public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, - @RequestParam String checksum, + @RequestParam(required = false) String checksum, @RequestParam(required = false) String checksumAlgorithm, - @RequestBody MultipartFile firmwareFile) throws ThingsboardException { + @RequestBody MultipartFile file) throws ThingsboardException { checkParameter(FIRMWARE_ID, strFirmwareId); - checkParameter("checksum", checksum); try { FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ); @@ -130,9 +129,9 @@ public class FirmwareController extends BaseController { firmware.setChecksumAlgorithm(checksumAlgorithm); firmware.setChecksum(checksum); - firmware.setFileName(firmwareFile.getOriginalFilename()); - firmware.setContentType(firmwareFile.getContentType()); - firmware.setData(ByteBuffer.wrap(firmwareFile.getBytes())); + firmware.setFileName(file.getOriginalFilename()); + firmware.setContentType(file.getContentType()); + firmware.setData(ByteBuffer.wrap(file.getBytes())); return firmwareService.saveFirmware(firmware); } catch (Exception e) { throw handleException(e); @@ -186,4 +185,4 @@ public class FirmwareController extends BaseController { } } -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 9f484c8964..da5abf7549 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -206,16 +206,16 @@ public class BaseFirmwareService implements FirmwareService { throw new DataValidationException("Firmware content type should be specified!"); } - if (StringUtils.isEmpty(firmware.getChecksum())) { - throw new DataValidationException("Firmware checksum should be specified!"); - } - ByteBuffer data = firmware.getData(); if (data == null || !data.hasArray() || data.array().length == 0) { throw new DataValidationException("Firmware data should be specified!"); } if (firmware.getChecksumAlgorithm() != null) { + if (StringUtils.isEmpty(firmware.getChecksum())) { + throw new DataValidationException("Firmware checksum should be specified!"); + } + HashFunction hashFunction; switch (firmware.getChecksumAlgorithm()) { case "sha256": @@ -232,7 +232,6 @@ public class BaseFirmwareService implements FirmwareService { } String currentChecksum = hashFunction.hashBytes(data.array()).toString(); - ; if (!currentChecksum.equals(firmware.getChecksum())) { throw new DataValidationException("Wrong firmware file!"); diff --git a/ui-ngx/src/app/core/http/firmware.service.ts b/ui-ngx/src/app/core/http/firmware.service.ts new file mode 100644 index 0000000000..0177cd4cfc --- /dev/null +++ b/ui-ngx/src/app/core/http/firmware.service.ts @@ -0,0 +1,123 @@ +/// +/// Copyright © 2016-2021 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 { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { PageLink } from '@shared/models/page/page-link'; +import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils'; +import { Observable } from 'rxjs'; +import { PageData } from '@shared/models/page/page-data'; +import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; +import { catchError, map, mergeMap } from 'rxjs/operators'; +import { deepClone, isDefinedAndNotNull } from '@core/utils'; +import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; +import { InterceptorConfig } from '@core/interceptors/interceptor-config'; + +@Injectable({ + providedIn: 'root' +}) +export class FirmwareService { + constructor( + private http: HttpClient + ) { + + } + + public getFirmwares(pageLink: PageLink, hasData?: boolean, config?: RequestConfig): Observable> { + let url = `/api/firmwares${pageLink.toQuery()}`; + if (isDefinedAndNotNull(hasData)) { + url += `&hasData=${hasData}`; + } + return this.http.get>(url, defaultHttpOptionsFromConfig(config)); + } + + public getFirmware(firmwareId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config)); + } + + public getFirmwareInfo(firmwareId: string, config?: RequestConfig): Observable { + return this.http.get(`/api/firmware/info/${firmwareId}`, defaultHttpOptionsFromConfig(config)); + } + + public downloadFirmware(firmwareId: string): Observable { + return this.http.get(`/api/firmware/${firmwareId}/download`, { responseType: 'arraybuffer', observe: 'response' }).pipe( + map((response) => { + const headers = response.headers; + const filename = headers.get('x-filename'); + const contentType = headers.get('content-type'); + const linkElement = document.createElement('a'); + try { + const blob = new Blob([response.body], { type: contentType }); + const url = URL.createObjectURL(blob); + linkElement.setAttribute('href', url); + linkElement.setAttribute('download', filename); + const clickEvent = new MouseEvent('click', + { + view: window, + bubbles: true, + cancelable: false + } + ); + linkElement.dispatchEvent(clickEvent); + return null; + } catch (e) { + throw e; + } + }) + ); + } + + public saveFirmware(firmware: Firmware, config?: RequestConfig): Observable { + if (!firmware.file) { + return this.saveFirmwareInfo(firmware, config); + } + const firmwareInfo = deepClone(firmware); + delete firmwareInfo.file; + delete firmwareInfo.checksum; + delete firmwareInfo.checksumAlgorithm; + return this.saveFirmwareInfo(firmwareInfo, config).pipe( + mergeMap(res => { + return this.uploadFirmwareFile(res.id.id, firmware.file, firmware.checksumAlgorithm, firmware.checksum).pipe( + catchError(() => this.deleteFirmware(res.id.id)) + ); + }) + ); + } + + public saveFirmwareInfo(firmware: FirmwareInfo, config?: RequestConfig): Observable { + return this.http.post('/api/firmware', firmware, defaultHttpOptionsFromConfig(config)); + } + + public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm?: string, + checksum?: string, config?: RequestConfig): Observable { + if (!config) { + config = {}; + } + const formData = new FormData(); + formData.append('file', file); + let url = `/api/firmware/${firmwareId}`; + if (checksumAlgorithm && checksum) { + url += `?checksumAlgorithm=${checksumAlgorithm}&checksum=${checksum}`; + } + return this.http.post(url, formData, + defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest)); + } + + public deleteFirmware(firmwareId: string, config?: RequestConfig) { + return this.http.delete(`/api/firmware/${firmwareId}`, defaultHttpOptionsFromConfig(config)); + } + +} diff --git a/ui-ngx/src/app/core/http/http-utils.ts b/ui-ngx/src/app/core/http/http-utils.ts index 69f9912828..5927e59d45 100644 --- a/ui-ngx/src/app/core/http/http-utils.ts +++ b/ui-ngx/src/app/core/http/http-utils.ts @@ -39,3 +39,11 @@ export function defaultHttpOptions(ignoreLoading: boolean = false, params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) }; } + +export function defaultHttpUploadOptions(ignoreLoading: boolean = false, + ignoreErrors: boolean = false, + resendRequest: boolean = false) { + return { + params: new InterceptorHttpParams(new InterceptorConfig(ignoreLoading, ignoreErrors, resendRequest)) + }; +} diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 7783fdd2c3..b14478f7e4 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -279,6 +279,13 @@ export class MenuService { icon: 'mdi:alpha-d-box', isMdiIcon: true }, + { + id: guid(), + name: 'firmware.firmware', + type: 'link', + path: '/firmwares', + icon: 'memory' + }, { id: guid(), name: 'entity-view.entity-views', @@ -379,6 +386,11 @@ export class MenuService { icon: 'mdi:alpha-d-box', isMdiIcon: true, path: '/deviceProfiles' + }, + { + name: 'firmware.firmware', + icon: 'memory', + path: '/firmwares' } ] }, diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index b9c2697d0a..17c769e5ae 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -407,7 +407,7 @@ export function sortObjectKeys(obj: T): T { } export function deepTrim(obj: T): T { - if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null) { + if (isNumber(obj) || isUndefined(obj) || isString(obj) || obj === null || obj instanceof File) { return obj; } return Object.keys(obj).reduce((acc, curr) => { diff --git a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts index e1e3675e89..e6de5dbf84 100644 --- a/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts +++ b/ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts @@ -172,7 +172,7 @@ export class EntityTableConfig, P extends PageLink = P deleteEntityContent: EntityStringFunction = () => ''; deleteEntitiesTitle: EntityCountStringFunction = () => ''; deleteEntitiesContent: EntityCountStringFunction = () => ''; - loadEntity: EntityByIdOperation = () => of(); + loadEntity: EntityByIdOperation = () => of(); saveEntity: EntityTwoWayOperation = (entity) => of(entity); deleteEntity: EntityIdOneWayOperation = () => of(); entitiesFetchFunction: EntitiesFetchFunction = () => of(emptyPageData()); diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts new file mode 100644 index 0000000000..688f3bfc1f --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmware-routing.module.ts @@ -0,0 +1,48 @@ +/// +/// Copyright © 2016-2021 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 { RouterModule, Routes } from '@angular/router'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { Authority } from '@shared/models/authority.enum'; +import { NgModule } from '@angular/core'; +import { FirmwareTableConfigResolve } from '@home/pages/firmware/firmware-table-config.resolve'; + +const routes: Routes = [ + { + path: 'firmwares', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN], + title: 'firmware.firmware', + breadcrumb: { + label: 'firmware.firmware', + icon: 'memory' + } + }, + resolve: { + entitiesTableConfig: FirmwareTableConfigResolve + } + } +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + FirmwareTableConfigResolve + ] +}) +export class FirmwareRoutingModule{ } diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts new file mode 100644 index 0000000000..8fd2a8fccb --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts @@ -0,0 +1,99 @@ +/// +/// Copyright © 2016-2021 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 { Injectable } from '@angular/core'; +import { Resolve } from '@angular/router'; +import { + DateEntityTableColumn, + EntityTableColumn, + EntityTableConfig +} from '@home/models/entity/entities-table-config.models'; +import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; +import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; +import { TranslateService } from '@ngx-translate/core'; +import { DatePipe } from '@angular/common'; +import { FirmwareService } from '@core/http/firmware.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component'; +import { EntityAction } from '@home/models/entity/entity-component.models'; +import { DeviceInfo } from '@shared/models/device.models'; + +@Injectable() +export class FirmwareTableConfigResolve implements Resolve> { + + private readonly config: EntityTableConfig = new EntityTableConfig(); + + constructor(private translate: TranslateService, + private datePipe: DatePipe, + private firmwareService: FirmwareService) { + this.config.entityType = EntityType.FIRMWARE; + this.config.entityComponent = FirmwaresComponent; + this.config.entityTranslations = entityTypeTranslations.get(EntityType.FIRMWARE); + this.config.entityResources = entityTypeResources.get(EntityType.FIRMWARE); + + this.config.entityTitle = (firmware) => firmware ? firmware.title : ''; + + this.config.columns.push( + new DateEntityTableColumn('createdTime', 'common.created-time', this.datePipe, '150px'), + new EntityTableColumn('title', 'firmware.title', '50%'), + new EntityTableColumn('version', 'firmware.version', '50%') + ); + + this.config.cellActionDescriptors.push( + { + name: this.translate.instant('firmware.export'), + icon: 'file_download', + isEnabled: (firmware) => firmware.hasData, + onAction: ($event, entity) => this.exportFirmware($event, entity) + } + ); + + this.config.deleteEntityTitle = firmware => this.translate.instant('firmware.delete-firmware-title', + { firmwareTitle: firmware.title }); + this.config.deleteEntityContent = () => this.translate.instant('firmware.delete-firmware-text'); + this.config.deleteEntitiesTitle = count => this.translate.instant('firmware.delete-firmwares-title', {count}); + this.config.deleteEntitiesContent = () => this.translate.instant('firmware.delete-firmwares-text'); + + this.config.entitiesFetchFunction = pageLink => this.firmwareService.getFirmwares(pageLink); + this.config.loadEntity = id => this.firmwareService.getFirmwareInfo(id.id); + this.config.saveEntity = firmware => this.firmwareService.saveFirmware(firmware); + this.config.deleteEntity = id => this.firmwareService.deleteFirmware(id.id); + + this.config.onEntityAction = action => this.onFirmwareAction(action); + } + + resolve(): EntityTableConfig { + this.config.tableTitle = this.translate.instant('firmware.firmware'); + return this.config; + } + + exportFirmware($event: Event, firmware: FirmwareInfo) { + if ($event) { + $event.stopPropagation(); + } + this.firmwareService.downloadFirmware(firmware.id.id).subscribe(); + } + + onFirmwareAction(action: EntityAction): boolean { + switch (action.action) { + case 'uploadFirmware': + this.exportFirmware(action.event, action.entity); + return true; + } + return false; + } + +} diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts new file mode 100644 index 0000000000..ab1b8343b2 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmware.module.ts @@ -0,0 +1,35 @@ +/// +/// Copyright © 2016-2021 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 { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@shared/shared.module'; +import { HomeComponentsModule } from '@home/components/home-components.module'; +import { FirmwareRoutingModule } from '@home/pages/firmware/firmware-routing.module'; +import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component'; + +@NgModule({ + declarations: [ + FirmwaresComponent + ], + imports: [ + CommonModule, + SharedModule, + HomeComponentsModule, + FirmwareRoutingModule + ] +}) +export class FirmwareModule { } diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html new file mode 100644 index 0000000000..6f43e4a4cc --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html @@ -0,0 +1,87 @@ + +
+ + +
+
+
+
+ firmware.warning-after-save-no-edit +
+ + firmware.title + + + {{ 'firmware.title-required' | translate }} + + + + firmware.version + + + {{ 'firmware.version-required' | translate }} + + +
+
+
+ + firmware.checksum-algorithm + + + + {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} + + + + + firmware.checksum + + + {{ 'firmware.checksum-required' | translate }} + + +
+ + +
+
+ + firmware.description + + +
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts new file mode 100644 index 0000000000..e1684c3031 --- /dev/null +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts @@ -0,0 +1,112 @@ +/// +/// Copyright © 2016-2021 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, OnDestroy, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { EntityComponent } from '@home/components/entity/entity.component'; +import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models'; +import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-firmware', + templateUrl: './firmwares.component.html' +}) +export class FirmwaresComponent extends EntityComponent implements OnInit, OnDestroy { + + private destroy$ = new Subject(); + + checksumAlgorithms = Object.values(ChecksumAlgorithm); + checksumAlgorithmTranslationMap = ChecksumAlgorithmTranslationMap; + + constructor(protected store: Store, + protected translate: TranslateService, + @Inject('entity') protected entityValue: Firmware, + @Inject('entitiesTableConfig') protected entitiesTableConfigValue: EntityTableConfig, + public fb: FormBuilder) { + super(store, fb, entityValue, entitiesTableConfigValue); + } + + ngOnInit() { + super.ngOnInit(); + if (this.isAdd) { + this.entityForm.get('checksumAlgorithm').valueChanges.pipe( + map(algorithm => !!algorithm), + distinctUntilChanged(), + takeUntil(this.destroy$) + ).subscribe( + setAlgorithm => { + if (setAlgorithm) { + this.entityForm.get('checksum').setValidators([Validators.maxLength(1020), Validators.required]); + } else { + this.entityForm.get('checksum').clearValidators(); + } + this.entityForm.get('checksum').updateValueAndValidity({emitEvent: false}); + } + ); + } + } + + ngOnDestroy() { + super.ngOnDestroy(); + this.destroy$.next(); + this.destroy$.complete(); + } + + hideDelete() { + if (this.entitiesTableConfig) { + return !this.entitiesTableConfig.deleteEnabled(this.entity); + } else { + return false; + } + } + + buildForm(entity: Firmware): FormGroup { + const form = this.fb.group({ + title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], + version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]], + additionalInfo: this.fb.group( + { + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], + } + ) + }); + if (this.isAdd) { + form.addControl('checksumAlgorithm', this.fb.control(null)); + form.addControl('checksum', this.fb.control('', Validators.maxLength(1020))); + form.addControl('file', this.fb.control(null, Validators.required)); + } + return form; + } + + updateForm(entity: Firmware) { + if (this.isEdit) { + this.entityForm.get('title').disable({emitEvent: false}); + this.entityForm.get('version').disable({emitEvent: false}); + } + this.entityForm.patchValue({ + title: entity.title, + version: entity.version, + additionalInfo: { + description: entity.additionalInfo ? entity.additionalInfo.description : '' + } + }); + } +} diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index d823c36bc8..f1d9de02d4 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -35,6 +35,7 @@ import { modulesMap } from '../../common/modules-map'; import { DeviceProfileModule } from './device-profile/device-profile.module'; import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; import { ResourceModule } from '@home/pages/resource/resource.module'; +import { FirmwareModule } from '@home/pages/firmware/firmware.module'; @NgModule({ exports: [ @@ -54,6 +55,7 @@ import { ResourceModule } from '@home/pages/resource/resource.module'; AuditLogModule, ApiUsageModule, ResourceModule, + FirmwareModule, UserModule ], providers: [ diff --git a/ui-ngx/src/app/shared/components/file-input.component.ts b/ui-ngx/src/app/shared/components/file-input.component.ts index 0fcaf8b790..d70837ac82 100644 --- a/ui-ngx/src/app/shared/components/file-input.component.ts +++ b/ui-ngx/src/app/shared/components/file-input.component.ts @@ -105,6 +105,9 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, @Input() readAsBinary = false; + @Input() + workFromFileObj = false; + private multipleFileValue = false; @Input() @@ -124,6 +127,7 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, fileName: string | string[]; fileContent: any; + files: File[]; @ViewChild('flow', {static: true}) flow: FlowDirective; @@ -151,15 +155,17 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, } }); if (readers.length) { - Promise.all(readers).then((filesContent) => { - filesContent = filesContent.filter(content => content.fileContent != null); - if (filesContent.length === 1) { - this.fileContent = filesContent[0].fileContent; - this.fileName = filesContent[0].fileName; + Promise.all(readers).then((files) => { + files = files.filter(file => file.fileContent != null || file.files != null); + if (files.length === 1) { + this.fileContent = files[0].fileContent; + this.fileName = files[0].fileName; + this.files = files[0].files; this.updateModel(); - } else if (filesContent.length > 1) { - this.fileContent = filesContent.map(content => content.fileContent); - this.fileName = filesContent.map(content => content.fileName); + } else if (files.length > 1) { + this.fileContent = files.map(content => content.fileContent); + this.fileName = files.map(content => content.fileName); + this.files = files.map(content => content.files); this.updateModel(); } }); @@ -177,21 +183,27 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, reader.onload = () => { let fileName = null; let fileContent = null; + let files = null; if (typeof reader.result === 'string') { fileContent = reader.result; if (fileContent && fileContent.length > 0) { - if (this.contentConvertFunction) { - fileContent = this.contentConvertFunction(fileContent); - } - if (fileContent) { + if (!this.workFromFileObj) { + if (this.contentConvertFunction) { + fileContent = this.contentConvertFunction(fileContent); + } + if (fileContent) { + fileName = file.name; + } + } else { + files = file.file; fileName = file.name; } } } - resolve({fileContent, fileName}); + resolve({fileContent, fileName, files}); }; reader.onerror = () => { - resolve({fileContent: null, fileName: null}); + resolve({fileContent: null, fileName: null, files: null}); }; if (this.readAsBinary) { reader.readAsBinaryString(file.file); @@ -227,7 +239,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, } writeValue(value: any): void { - this.fileName = this.existingFileName || null; + let fileName = null; + if (this.workFromFileObj && value instanceof File) { + fileName = Array.isArray(value) ? value.map(file => file.name) : value.name; + } + this.fileName = this.existingFileName || fileName; } ngOnChanges(changes: SimpleChanges): void { @@ -242,13 +258,18 @@ export class FileInputComponent extends PageComponent implements AfterViewInit, } private updateModel() { - this.propagateChange(this.fileContent); - this.fileNameChanged.emit(this.fileName); + if (this.workFromFileObj) { + this.propagateChange(this.files); + } else { + this.propagateChange(this.fileContent); + this.fileNameChanged.emit(this.fileName); + } } clearFile() { this.fileName = null; this.fileContent = null; + this.files = null; this.updateModel(); } diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 846aa4e6dc..2ee8b226cc 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -33,7 +33,8 @@ export enum EntityType { WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', WIDGET_TYPE = 'WIDGET_TYPE', API_USAGE_STATE = 'API_USAGE_STATE', - TB_RESOURCE = 'TB_RESOURCE' + TB_RESOURCE = 'TB_RESOURCE', + FIRMWARE = 'FIRMWARE' } export enum AliasEntityType { @@ -278,6 +279,16 @@ export const entityTypeTranslations = new Map( + [ + [ChecksumAlgorithm.MD5, 'MD5'], + [ChecksumAlgorithm.SHA256, 'SHA-256'], + [ChecksumAlgorithm.CRC32, 'CRC-32'] + ] +); + +export interface FirmwareInfo extends BaseData { + tenantId?: TenantId; + title?: string; + version?: string; + hasData?: boolean; + additionalInfo?: any; +} + +export interface Firmware extends FirmwareInfo { + file?: File; + data: string; + fileName: string; + checksum?: ChecksumAlgorithm; + checksumAlgorithm?: string; + contentType: string; +} diff --git a/ui-ngx/src/app/shared/models/id/firmware-id.ts b/ui-ngx/src/app/shared/models/id/firmware-id.ts new file mode 100644 index 0000000000..31dd21210b --- /dev/null +++ b/ui-ngx/src/app/shared/models/id/firmware-id.ts @@ -0,0 +1,26 @@ +/// +/// Copyright © 2016-2021 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 { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; + +export class FirmwareId implements EntityId { + entityType = EntityType.FIRMWARE; + id: string; + constructor(id: string) { + this.id = id; + } +} 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 e645084c26..3cddae95fa 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1693,6 +1693,33 @@ "inherit-owner": "Inherit from owner", "source-attribute-not-set": "If source attribute isn't set" }, + "firmware": { + "add": "Add firmware", + "checksum": "Checksum", + "checksum-required": "Checksum is required.", + "checksum-algorithm": "Checksum algorithm", + "description": "Description", + "delete": "Delete firmware", + "delete-firmware-text": "Be careful, after the confirmation the firmware will become unrecoverable.", + "delete-firmware-title": "Are you sure you want to delete the firmware '{{firmwareTitle}}'?", + "delete-firmwares-action-title": "Delete { count, plural, 1 {1 firmware} other {# firmwares} }", + "delete-firmwares-text": "Be careful, after the confirmation all selected resources will be removed.", + "delete-firmwares-title": "Are you sure you want to delete { count, plural, 1 {1 firmware} other {# firmwares} }?", + "drop-file": "Drop a firmware file or click to select a file to upload.", + "empty": "Firmware is empty", + "export": "Export firmware", + "no-firmware-matching": "No firmware matching '{{firmware}}' were found.", + "no-firmware-text": "No firmwares found", + "firmware": "Firmware", + "firmware-details": "Firmware details", + "search": "Search firmwares", + "selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected", + "title": "Title", + "title-required": "Title is required.", + "version": "Version", + "version-required": "Version is required.", + "warning-after-save-no-edit": "Once the firmware is saved, it will not be possible to change the title and version fields." + }, "fullscreen": { "expand": "Expand to fullscreen", "exit": "Exit fullscreen", From 401e41f7c355703727a8ae9df0d2200010198458 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 12 Apr 2021 17:50:12 +0300 Subject: [PATCH 016/127] implemented FirmwareControllerSqlTest --- .../BaseFirmwareControllerTest.java | 302 ++++++++++++++++++ .../sql/FirmwareControllerSqlTest.java | 23 ++ .../server/common/data/FirmwareInfo.java | 1 + .../dao/service/BaseFirmwareServiceTest.java | 60 +++- 4 files changed, 374 insertions(+), 12 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java create mode 100644 application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java new file mode 100644 index 0000000000..425a21e1a2 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java @@ -0,0 +1,302 @@ +/** + * Copyright © 2016-2021 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.controller; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.Authority; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public abstract class BaseFirmwareControllerTest extends AbstractControllerTest { + + private IdComparator idComparator = new IdComparator<>(); + + public static final String TITLE = "My firmware"; + private static final String FILE_NAME = "filename.txt"; + private static final String VERSION = "v1.0"; + private static final String CONTENT_TYPE = "text/plain"; + private static final String CHECKSUM_ALGORITHM = "sha256"; + private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; + private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1}); + + private Tenant savedTenant; + private User tenantAdmin; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); + } + + @Test + public void testSaveFirmware() throws Exception { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION); + + FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + + Assert.assertNotNull(savedFirmwareInfo); + Assert.assertNotNull(savedFirmwareInfo.getId()); + Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0); + Assert.assertEquals(savedTenant.getId(), savedFirmwareInfo.getTenantId()); + Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); + Assert.assertEquals(firmwareInfo.getVersion(), savedFirmwareInfo.getVersion()); + + savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); + + save(savedFirmwareInfo); + + FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); + Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); + } + + @Test + public void testSaveFirmwareData() throws Exception { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION); + + FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + + Assert.assertNotNull(savedFirmwareInfo); + Assert.assertNotNull(savedFirmwareInfo.getId()); + Assert.assertTrue(savedFirmwareInfo.getCreatedTime() > 0); + Assert.assertEquals(savedTenant.getId(), savedFirmwareInfo.getTenantId()); + Assert.assertEquals(firmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); + Assert.assertEquals(firmwareInfo.getVersion(), savedFirmwareInfo.getVersion()); + + savedFirmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); + + save(savedFirmwareInfo); + + FirmwareInfo foundFirmwareInfo = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); + Assert.assertEquals(foundFirmwareInfo.getTitle(), savedFirmwareInfo.getTitle()); + + MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); + + Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + + Assert.assertEquals(FILE_NAME, savedFirmware.getFileName()); + Assert.assertEquals(CONTENT_TYPE, savedFirmware.getContentType()); + } + + @Test + public void testUpdateFirmwareFromDifferentTenant() throws Exception { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION); + + FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + + loginDifferentTenant(); + doPost("/api/firmware", savedFirmwareInfo, FirmwareInfo.class, status().isForbidden()); + deleteDifferentTenant(); + } + + @Test + public void testFindFirmwareInfoById() throws Exception { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION); + + FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + + FirmwareInfo foundFirmware = doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString(), FirmwareInfo.class); + Assert.assertNotNull(foundFirmware); + Assert.assertEquals(savedFirmwareInfo, foundFirmware); + } + + @Test + public void testFindFirmwareById() throws Exception { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION); + + FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + + MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); + + Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + + Firmware foundFirmware = doGet("/api/firmware/" + savedFirmwareInfo.getId().getId().toString(), Firmware.class); + Assert.assertNotNull(foundFirmware); + Assert.assertEquals(savedFirmware, foundFirmware); + } + + @Test + public void testDeleteFirmware() throws Exception { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION); + + FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + + doDelete("/api/firmware/" + savedFirmwareInfo.getId().getId().toString()) + .andExpect(status().isOk()); + + doGet("/api/firmware/info/" + savedFirmwareInfo.getId().getId().toString()) + .andExpect(status().isNotFound()); + } + + @Test + public void testFindTenantFirmwares() throws Exception { + List firmwares = new ArrayList<>(); + for (int i = 0; i < 165; i++) { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION + i); + + FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + + if (i > 100) { + MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); + + Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + firmwares.add(new FirmwareInfo(savedFirmware)); + } else { + firmwares.add(savedFirmwareInfo); + } + } + + List loadedFirmwares = new ArrayList<>(); + PageLink pageLink = new PageLink(24); + PageData pageData; + do { + pageData = doGetTypedWithPageLink("/api/firmwares?", + new TypeReference<>() { + }, pageLink); + loadedFirmwares.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(firmwares, idComparator); + Collections.sort(loadedFirmwares, idComparator); + + Assert.assertEquals(firmwares, loadedFirmwares); + } + + @Test + public void testFindTenantFirmwaresByHasData() throws Exception { + List firmwaresWithData = new ArrayList<>(); + List firmwaresWithoutData = new ArrayList<>(); + + for (int i = 0; i < 165; i++) { + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION + i); + + FirmwareInfo savedFirmwareInfo = save(firmwareInfo); + + if (i > 100) { + MockMultipartFile testData = new MockMultipartFile("file", FILE_NAME, CONTENT_TYPE, DATA.array()); + + Firmware savedFirmware = savaData("/api/firmware/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, CHECKSUM_ALGORITHM); + firmwaresWithData.add(new FirmwareInfo(savedFirmware)); + } else { + firmwaresWithoutData.add(savedFirmwareInfo); + } + } + + List loadedFirmwaresWithData = new ArrayList<>(); + PageLink pageLink = new PageLink(24); + PageData pageData; + do { + pageData = doGetTypedWithPageLink("/api/firmwares/true?", + new TypeReference<>() { + }, pageLink); + loadedFirmwaresWithData.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + List loadedFirmwaresWithoutData = new ArrayList<>(); + pageLink = new PageLink(24); + do { + pageData = doGetTypedWithPageLink("/api/firmwares/false?", + new TypeReference<>() { + }, pageLink); + loadedFirmwaresWithoutData.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(firmwaresWithData, idComparator); + Collections.sort(firmwaresWithoutData, idComparator); + Collections.sort(loadedFirmwaresWithData, idComparator); + Collections.sort(loadedFirmwaresWithoutData, idComparator); + + Assert.assertEquals(firmwaresWithData, loadedFirmwaresWithData); + Assert.assertEquals(firmwaresWithoutData, loadedFirmwaresWithoutData); + } + + + private FirmwareInfo save(FirmwareInfo firmwareInfo) throws Exception { + return doPost("/api/firmware", firmwareInfo, FirmwareInfo.class); + } + + protected Firmware savaData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { + MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); + postRequest.file(content); + setJwtToken(postRequest); + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), Firmware.class); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java b/application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java new file mode 100644 index 0000000000..a0e4a838ca --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/controller/sql/FirmwareControllerSqlTest.java @@ -0,0 +1,23 @@ +/** + * Copyright © 2016-2021 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.controller.sql; + +import org.thingsboard.server.controller.BaseFirmwareControllerTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class FirmwareControllerSqlTest extends BaseFirmwareControllerTest { +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java index bba9583d18..224e94b59c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -46,6 +46,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo this.tenantId = firmwareInfo.getTenantId(); this.title = firmwareInfo.getTitle(); this.version = firmwareInfo.getVersion(); + this.hasData = firmwareInfo.isHasData(); } @Override diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java index c4f80c551a..54033e29a7 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java @@ -336,6 +336,20 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); } + @Test + public void testFindFirmwareInfoById() { + FirmwareInfo firmware = new FirmwareInfo(); + firmware.setTenantId(tenantId); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION); + FirmwareInfo savedFirmware = firmwareService.saveFirmwareInfo(firmware); + + FirmwareInfo foundFirmware = firmwareService.findFirmwareInfoById(tenantId, savedFirmware.getId()); + Assert.assertNotNull(foundFirmware); + Assert.assertEquals(savedFirmware, foundFirmware); + firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); + } + @Test public void testDeleteFirmware() { Firmware firmware = new Firmware(); @@ -403,24 +417,46 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public void testFindTenantFirmwaresByTenantIdAndHasData() { List firmwares = new ArrayList<>(); for (int i = 0; i < 165; i++) { - Firmware firmware = new Firmware(); - firmware.setTenantId(tenantId); - firmware.setTitle(TITLE); - firmware.setVersion(VERSION + i); + FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setTenantId(tenantId); + firmwareInfo.setTitle(TITLE); + firmwareInfo.setVersion(VERSION + i); + firmwares.add(firmwareService.saveFirmwareInfo(firmwareInfo)); + } + + List loadedFirmwares = new ArrayList<>(); + PageLink pageLink = new PageLink(16); + PageData pageData; + do { + pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, false, pageLink); + loadedFirmwares.addAll(pageData.getData()); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + + Collections.sort(firmwares, idComparator); + Collections.sort(loadedFirmwares, idComparator); + + Assert.assertEquals(firmwares, loadedFirmwares); + + firmwares.forEach(f -> { + Firmware firmware = new Firmware(f.getId()); + firmware.setCreatedTime(f.getCreatedTime()); + firmware.setTenantId(f.getTenantId()); + firmware.setTitle(f.getTitle()); + firmware.setVersion(f.getVersion()); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); + firmwareService.saveFirmware(firmware); + f.setHasData(true); + }); - FirmwareInfo info = new FirmwareInfo(firmwareService.saveFirmware(firmware)); - info.setHasData(true); - firmwares.add(info); - } - - List loadedFirmwares = new ArrayList<>(); - PageLink pageLink = new PageLink(16); - PageData pageData; + loadedFirmwares = new ArrayList<>(); + pageLink = new PageLink(16); do { pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, true, pageLink); loadedFirmwares.addAll(pageData.getData()); From 7b2e512cd8cfacfa7025566373c92d835ed439af Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 12 Apr 2021 19:08:27 +0300 Subject: [PATCH 017/127] added multipart max size 50MB --- application/src/main/resources/thingsboard.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index e03b2d848e..01b2eb47b6 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -437,6 +437,9 @@ spring.resources.chain: content: enabled: "true" +spring.servlet.multipart.max-file-size: "50MB" +spring.servlet.multipart.max-request-size: "50MB" + spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true" spring.jpa.properties.hibernate.order_by.default_null_ordering: "last" From e31a95619c0cfffb9094c8be8ff37c8062dcc9cf Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 13 Apr 2021 15:10:13 +0300 Subject: [PATCH 018/127] UI: Added firmware autocomplete component; Add firmware to device and device profile --- ui-ngx/src/app/core/http/entity.service.ts | 9 + ui-ngx/src/app/core/http/firmware.service.ts | 7 +- .../firmware-autocomplete.component.html | 54 ++++ .../firmware-autocomplete.component.ts | 236 ++++++++++++++++++ .../home/components/home-components.module.ts | 7 +- .../add-device-profile-dialog.component.html | 4 + .../add-device-profile-dialog.component.ts | 8 +- .../profile/device-profile.component.html | 4 + .../profile/device-profile.component.ts | 6 +- .../device-wizard-dialog.component.html | 4 + .../wizard/device-wizard-dialog.component.ts | 4 +- .../home/pages/device/device.component.html | 4 + .../home/pages/device/device.component.ts | 2 + .../pages/firmware/firmwares.component.html | 10 + .../pages/firmware/firmwares.component.ts | 12 + ui-ngx/src/app/shared/models/device.models.ts | 3 + .../assets/locale/locale.constant-en_US.json | 5 +- 17 files changed, 366 insertions(+), 13 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.html create mode 100644 ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.ts diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 0862e10a76..2d9b9808ed 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -74,6 +74,7 @@ import { StringOperation } from '@shared/models/query/query.models'; import { alarmFields } from '@shared/models/alarm.models'; +import { FirmwareService } from '@core/http/firmware.service'; @Injectable({ providedIn: 'root' @@ -93,6 +94,7 @@ export class EntityService { private dashboardService: DashboardService, private entityRelationService: EntityRelationService, private attributeService: AttributeService, + private firmwareService: FirmwareService, private utils: UtilsService ) { } @@ -128,6 +130,9 @@ export class EntityService { case EntityType.ALARM: console.error('Get Alarm Entity is not implemented!'); break; + case EntityType.FIRMWARE: + observable = this.firmwareService.getFirmwareInfo(entityId, config); + break; } return observable; } @@ -326,6 +331,10 @@ export class EntityService { case EntityType.ALARM: console.error('Get Alarm Entities is not implemented!'); break; + case EntityType.FIRMWARE: + pageLink.sortOrder.property = 'title'; + entitiesObservable = this.firmwareService.getFirmwares(pageLink, true, config); + break; } return entitiesObservable; } diff --git a/ui-ngx/src/app/core/http/firmware.service.ts b/ui-ngx/src/app/core/http/firmware.service.ts index 0177cd4cfc..7bf42fb49e 100644 --- a/ui-ngx/src/app/core/http/firmware.service.ts +++ b/ui-ngx/src/app/core/http/firmware.service.ts @@ -23,8 +23,6 @@ import { PageData } from '@shared/models/page/page-data'; import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { deepClone, isDefinedAndNotNull } from '@core/utils'; -import { InterceptorHttpParams } from '@core/interceptors/interceptor-http-params'; -import { InterceptorConfig } from '@core/interceptors/interceptor-config'; @Injectable({ providedIn: 'root' @@ -37,10 +35,11 @@ export class FirmwareService { } public getFirmwares(pageLink: PageLink, hasData?: boolean, config?: RequestConfig): Observable> { - let url = `/api/firmwares${pageLink.toQuery()}`; + let url = `/api/firmwares`; if (isDefinedAndNotNull(hasData)) { - url += `&hasData=${hasData}`; + url += `/${hasData}`; } + url += `${pageLink.toQuery()}`; return this.http.get>(url, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.html new file mode 100644 index 0000000000..4e733b3132 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.html @@ -0,0 +1,54 @@ + + + + + + + + + +
+
+ firmware.no-firmware-text +
+ + + {{ translate.get('firmware.no-firmware-matching', + {entity: truncate.transform(searchText, true, 6, '...')}) | async }} + + +
+
+
+ + {{ requiredErrorText | translate }} + +
diff --git a/ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.ts new file mode 100644 index 0000000000..79dc170c7b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.ts @@ -0,0 +1,236 @@ +/// +/// Copyright © 2016-2021 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, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { Observable } from 'rxjs'; +import { map, mergeMap, share, tap } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { TranslateService } from '@ngx-translate/core'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { EntityId } from '@shared/models/id/entity-id'; +import { EntityType } from '@shared/models/entity-type.models'; +import { BaseData } from '@shared/models/base-data'; +import { EntityService } from '@core/http/entity.service'; +import { TruncatePipe } from '@shared/pipe/truncate.pipe'; +import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; +import { FirmwareInfo } from '@shared/models/firmware.models'; +import { FirmwareService } from '@core/http/firmware.service'; +import { PageLink } from '@shared/models/page/page-link'; +import { Direction } from '@shared/models/page/sort-order'; + +@Component({ + selector: 'tb-firmware-autocomplete', + templateUrl: './firmware-autocomplete.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => FirmwareAutocompleteComponent), + multi: true + }] +}) +export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnInit { + + firmwareFormGroup: FormGroup; + + modelValue: string | null; + + @Input() + labelText: string; + + @Input() + requiredText: string; + + @Input() + useFullEntityId = false; + + private requiredValue: boolean; + + get required(): boolean { + return this.requiredValue; + } + + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + @ViewChild('firmwareInput', {static: true}) firmwareInput: ElementRef; + @ViewChild('firmwareInput', {read: MatAutocompleteTrigger}) firmwareAutocomplete: MatAutocompleteTrigger; + + filteredFirmwares: Observable>; + + searchText = ''; + + private dirty = false; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + public translate: TranslateService, + public truncate: TruncatePipe, + private entityService: EntityService, + private firmwareService: FirmwareService, + private fb: FormBuilder) { + this.firmwareFormGroup = this.fb.group({ + firmwareId: [null] + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.filteredFirmwares = this.firmwareFormGroup.get('firmwareId').valueChanges + .pipe( + tap(value => { + let modelValue; + if (typeof value === 'string' || !value) { + modelValue = null; + } else { + modelValue = this.useFullEntityId ? value.id : value.id.id; + } + this.updateView(modelValue); + if (value === null) { + this.clear(); + } + }), + map(value => value ? (typeof value === 'string' ? value : value.title) : ''), + mergeMap(name => this.fetchFirmware(name)), + share() + ); + } + + ngAfterViewInit(): void { + } + + getCurrentEntity(): BaseData | null { + const currentRuleChain = this.firmwareFormGroup.get('firmwareId').value; + if (currentRuleChain && typeof currentRuleChain !== 'string') { + return currentRuleChain as BaseData; + } else { + return null; + } + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.firmwareFormGroup.disable({emitEvent: false}); + } else { + this.firmwareFormGroup.enable({emitEvent: false}); + } + } + + textIsNotEmpty(text: string): boolean { + return (text && text.length > 0); + } + + writeValue(value: string | EntityId | null): void { + this.searchText = ''; + if (value != null && value !== '') { + let firmwareId = ''; + if (typeof value === 'string') { + firmwareId = value; + } else if (value.entityType && value.id) { + firmwareId = value.id; + } + if (firmwareId !== '') { + this.entityService.getEntity(EntityType.FIRMWARE, firmwareId, {ignoreLoading: true, ignoreErrors: true}).subscribe( + (entity) => { + this.modelValue = entity.id.id; + this.firmwareFormGroup.get('firmwareId').patchValue(entity, {emitEvent: false}); + }, + () => { + this.modelValue = null; + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + if (value !== null) { + this.propagateChange(this.modelValue); + } + } + ); + } else { + this.modelValue = null; + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + } + } else { + this.modelValue = null; + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + } + this.dirty = true; + } + + onFocus() { + if (this.dirty) { + this.firmwareFormGroup.get('firmwareId').updateValueAndValidity({onlySelf: true, emitEvent: true}); + this.dirty = false; + } + } + + reset() { + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + } + + updateView(value: string | null) { + if (this.modelValue !== value) { + this.modelValue = value; + this.propagateChange(this.modelValue); + } + } + + displayFirmwareFn(firmware?: FirmwareInfo): string | undefined { + return firmware ? `${firmware.title} (${firmware.version})` : undefined; + } + + fetchFirmware(searchText?: string): Observable> { + this.searchText = searchText; + const pageLink = new PageLink(50, 0, searchText, { + property: 'title', + direction: Direction.ASC + }); + return this.firmwareService.getFirmwares(pageLink, true, {ignoreLoading: true}).pipe( + map((data) => data && data.data.length ? data.data : null) + ); + } + + clear() { + this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: true}); + setTimeout(() => { + this.firmwareInput.nativeElement.blur(); + this.firmwareInput.nativeElement.focus(); + }, 0); + } + + get placeholderText(): string { + return this.labelText || 'firmware.firmware'; + } + + get requiredErrorText(): string { + return this.requiredText || 'firmware.firmware-required'; + } + + firmwareTitleText(firmware: FirmwareInfo): string { + return `${firmware.title} (${firmware.version})`; + } +} 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 950ee285e5..ab707b6b07 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 @@ -134,6 +134,7 @@ import { DashboardStateDialogComponent } from '@home/components/dashboard-page/s import { EmbedDashboardDialogComponent } from '@home/components/widget/dialog/embed-dashboard-dialog.component'; import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/embed-dashboard-dialog-token'; import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; +import { FirmwareAutocompleteComponent } from '@home/components/firmware/firmware-autocomplete.component'; @NgModule({ declarations: @@ -247,7 +248,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag ManageDashboardStatesDialogComponent, DashboardStateDialogComponent, EmbedDashboardDialogComponent, - DisplayWidgetTypesPanelComponent + DisplayWidgetTypesPanelComponent, + FirmwareAutocompleteComponent ], imports: [ CommonModule, @@ -351,7 +353,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag ManageDashboardStatesDialogComponent, DashboardStateDialogComponent, EmbedDashboardDialogComponent, - DisplayWidgetTypesPanelComponent + DisplayWidgetTypesPanelComponent, + FirmwareAutocompleteComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index 23219f1074..a4f9fc3482 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -60,6 +60,10 @@ {{ 'device-profile.type-required' | translate }} + + device-profile.description diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index a13d15492c..83f248db92 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -48,7 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { deepTrim } from '@core/utils'; -import {ServiceType} from "@shared/models/queue.models"; +import { ServiceType } from '@shared/models/queue.models'; export interface AddDeviceProfileDialogData { deviceProfileName: string; @@ -72,13 +72,13 @@ export class AddDeviceProfileDialogComponent extends entityType = EntityType; - deviceProfileTypes = Object.keys(DeviceProfileType); + deviceProfileTypes = Object.values(DeviceProfileType); deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; deviceTransportTypeHints = deviceTransportTypeHintMap; - deviceTransportTypes = Object.keys(DeviceTransportType); + deviceTransportTypes = Object.values(DeviceTransportType); deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; @@ -108,6 +108,7 @@ export class AddDeviceProfileDialogComponent extends type: [DeviceProfileType.DEFAULT, [Validators.required]], defaultRuleChainId: [null, []], defaultQueueName: ['', []], + firmwareId: [null], description: ['', []] } ); @@ -186,6 +187,7 @@ export class AddDeviceProfileDialogComponent extends transportType: this.transportConfigFormGroup.get('transportType').value, provisionType: deviceProvisionConfiguration.type, provisionDeviceKey, + firmwareId: this.deviceProfileDetailsFormGroup.get('firmwareId').value, description: this.deviceProfileDetailsFormGroup.get('description').value, profileData: { configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 9ee6201db6..5f7593a03b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -63,6 +63,10 @@ [queueType]="serviceType" formControlName="defaultQueueName"> + + device-profile.type 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 28ac93c2af..23f1bd511c 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 @@ -53,11 +53,11 @@ export class DeviceProfileComponent extends EntityComponent { entityType = EntityType; - deviceProfileTypes = Object.keys(DeviceProfileType); + deviceProfileTypes = Object.values(DeviceProfileType); deviceProfileTypeTranslations = deviceProfileTypeTranslationMap; - deviceTransportTypes = Object.keys(DeviceTransportType); + deviceTransportTypes = Object.values(DeviceTransportType); deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; @@ -109,6 +109,7 @@ export class DeviceProfileComponent extends EntityComponent { }), defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], defaultQueueName: [entity ? entity.defaultQueueName : '', []], + firmwareId: [entity ? entity.firmwareId : null], description: [entity ? entity.description : '', []], } ); @@ -184,6 +185,7 @@ export class DeviceProfileComponent extends EntityComponent { }}, {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({firmwareId: entity.firmwareId}, {emitEvent: false}); this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 245cb6503b..416b006499 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -48,6 +48,10 @@ device.label + + device-profile.transport-type diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index 1cba31aa24..df925e4e21 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -70,7 +70,7 @@ export class DeviceWizardDialogComponent extends entityType = EntityType; - deviceTransportTypes = Object.keys(DeviceTransportType); + deviceTransportTypes = Object.values(DeviceTransportType); deviceTransportTypeTranslations = deviceTransportTypeTranslationMap; @@ -107,6 +107,7 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup = this.fb.group({ name: ['', Validators.required], label: [''], + firmwareId: [null], gateway: [false], overwriteActivityTime: [false], transportType: [DeviceTransportType.DEFAULT, Validators.required], @@ -312,6 +313,7 @@ export class DeviceWizardDialogComponent extends const device = { name: this.deviceWizardFormGroup.get('name').value, label: this.deviceWizardFormGroup.get('label').value, + firmwareId: this.deviceWizardFormGroup.get('firmwareId').value, deviceProfileId: profileId, additionalInfo: { gateway: this.deviceWizardFormGroup.get('gateway').value, 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 b01aced285..869b6a0c0a 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 @@ -95,6 +95,10 @@ device.label + + 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 261bb1eba5..699bfd2b4e 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 @@ -79,6 +79,7 @@ export class DeviceComponent extends EntityComponent { { name: [entity ? entity.name : '', [Validators.required]], deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]], + firmwareId: [entity ? entity.firmwareId : null], label: [entity ? entity.label : ''], deviceData: [entity ? entity.deviceData : null, [Validators.required]], additionalInfo: this.fb.group( @@ -95,6 +96,7 @@ export class DeviceComponent extends EntityComponent { updateForm(entity: DeviceInfo) { this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({deviceProfileId: entity.deviceProfileId}); + this.entityForm.patchValue({firmwareId: entity.firmwareId}); this.entityForm.patchValue({label: entity.label}); this.entityForm.patchValue({deviceData: entity.deviceData}); this.entityForm.patchValue({ diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html index 6f43e4a4cc..c85ac61f0b 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html @@ -28,6 +28,16 @@ [fxShow]="!hideDelete() && !isEdit"> {{'resource.delete' | translate }} +
+ +
diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts index e1684c3031..78d04ecf53 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts @@ -24,6 +24,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { EntityComponent } from '@home/components/entity/entity.component'; import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models'; import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; @Component({ selector: 'tb-firmware', @@ -109,4 +110,15 @@ export class FirmwaresComponent extends EntityComponent implements OnI } }); } + + onFirmwareIdCopied($event) { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('firmware.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 72b04828c5..7efac7feb0 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -27,6 +27,7 @@ import { KeyFilter } from '@shared/models/query/query.models'; import { TimeUnit } from '@shared/models/time/time.models'; import * as _moment from 'moment'; import { AbstractControl, ValidationErrors } from '@angular/forms'; +import { FirmwareId } from '@shared/models/id/firmware-id'; export enum DeviceProfileType { DEFAULT = 'DEFAULT' @@ -445,6 +446,7 @@ export interface DeviceProfile extends BaseData { provisionDeviceKey?: string; defaultRuleChainId?: RuleChainId; defaultQueueName?: string; + firmwareId?: FirmwareId; profileData: DeviceProfileData; } @@ -499,6 +501,7 @@ export interface Device extends BaseData { name: string; type: string; label: string; + firmwareId?: FirmwareId; deviceProfileId?: DeviceProfileId; deviceData?: DeviceData; additionalInfo?: 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 3cddae95fa..9012be0a99 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1698,6 +1698,7 @@ "checksum": "Checksum", "checksum-required": "Checksum is required.", "checksum-algorithm": "Checksum algorithm", + "copyId": "Copy firmware Id", "description": "Description", "delete": "Delete firmware", "delete-firmware-text": "Be careful, after the confirmation the firmware will become unrecoverable.", @@ -1708,10 +1709,12 @@ "drop-file": "Drop a firmware file or click to select a file to upload.", "empty": "Firmware is empty", "export": "Export firmware", - "no-firmware-matching": "No firmware matching '{{firmware}}' were found.", + "idCopiedMessage": "Firmware Id has been copied to clipboard", + "no-firmware-matching": "No firmware matching '{{entity}}' were found.", "no-firmware-text": "No firmwares found", "firmware": "Firmware", "firmware-details": "Firmware details", + "firmware-required": "Firmware is required.", "search": "Search firmwares", "selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected", "title": "Title", From d6f451ccca90ce6cd7c56d46f3b75ab804680b5e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 14 Apr 2021 16:36:34 +0300 Subject: [PATCH 019/127] created FirmwareStateService --- .../server/controller/BaseController.java | 4 + .../server/controller/DeviceController.java | 26 ++- .../controller/DeviceProfileController.java | 14 ++ .../server/controller/FirmwareController.java | 7 + .../firmware/DefaultFirmwareStateService.java | 171 ++++++++++++++++++ .../firmware/FirmwareStateService.java | 27 +++ .../server/common/data/DataConstants.java | 15 ++ .../dao/firmware/BaseFirmwareService.java | 48 ++--- 8 files changed, 286 insertions(+), 26 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 60c7e51aa4..d9d9a232a4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -119,6 +119,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.component.ComponentDiscoveryService; +import org.thingsboard.server.service.firmware.FirmwareStateService; import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.TbClusterService; @@ -240,6 +241,9 @@ public abstract class BaseController { @Autowired protected FirmwareService firmwareService; + @Autowired + protected FirmwareStateService firmwareStateService; + @Autowired protected TbQueueProducerProvider producerProvider; 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 c6409ebbad..0e4b758593 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -70,6 +70,7 @@ import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; @RestController @@ -117,13 +118,25 @@ public class DeviceController extends BaseController { checkEntity(device.getId(), device, Resource.DEVICE); + boolean created = device.getId() == null; + + boolean isFirmwareChanged = false; + + if (created) { + isFirmwareChanged = true; + } else { + Device oldDevice = deviceService.findDeviceById(getTenantId(), device.getId()); + if (!Objects.equals(device.getFirmwareId(), oldDevice.getFirmwareId()) || !oldDevice.getDeviceProfileId().equals(device.getDeviceProfileId())) { + isFirmwareChanged = true; + } + } + 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(), - device.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); + tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); logEntityAction(savedDevice.getId(), savedDevice, savedDevice.getCustomerId(), @@ -134,12 +147,19 @@ public class DeviceController extends BaseController { } else { deviceStateService.onDeviceUpdated(savedDevice); } + + if (isFirmwareChanged) { + firmwareStateService.update(savedDevice, created); + } + return savedDevice; - } catch (Exception e) { + } catch ( + Exception e) { logEntityAction(emptyId(EntityType.DEVICE), device, null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); throw handleException(e); } + } @PreAuthorize("hasAuthority('TENANT_ADMIN')") 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 81bf0ba1a7..c72ae1870d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -43,6 +43,7 @@ import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import java.util.List; +import java.util.Objects; import java.util.UUID; @RestController @@ -143,6 +144,15 @@ public class DeviceProfileController extends BaseController { checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); + boolean isFirmwareChanged = false; + + if (!created) { + DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfile.getId()); + if (!Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId())) { + isFirmwareChanged = true; + } + } + DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); tbClusterService.onDeviceProfileChange(savedDeviceProfile, null); @@ -153,6 +163,10 @@ public class DeviceProfileController extends BaseController { null, created ? ActionType.ADDED : ActionType.UPDATED, null); + if (isFirmwareChanged) { + firmwareStateService.update(savedDeviceProfile); + } + return savedDeviceProfile; } catch (Exception e) { logEntityAction(emptyId(EntityType.DEVICE_PROFILE), deviceProfile, diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index f9be6dad82..020a02b71c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.controller; +import com.google.common.hash.Hashing; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.core.io.ByteArrayResource; import org.springframework.http.HttpHeaders; import org.springframework.http.ResponseEntity; @@ -127,6 +129,11 @@ public class FirmwareController extends BaseController { firmware.setVersion(info.getVersion()); firmware.setAdditionalInfo(info.getAdditionalInfo()); + if (StringUtils.isEmpty(checksumAlgorithm)) { + checksumAlgorithm = "sha256"; + checksum = Hashing.sha256().hashBytes(file.getBytes()).toString(); + } + firmware.setChecksumAlgorithm(checksumAlgorithm); firmware.setChecksum(checksum); firmware.setFileName(file.getOriginalFilename()); diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java new file mode 100644 index 0000000000..3e03dd99e0 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -0,0 +1,171 @@ +/** + * Copyright © 2016-2021 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.firmware; + +import com.google.common.util.concurrent.FutureCallback; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; +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.Firmware; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.FirmwareId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.firmware.FirmwareService; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; + +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM; +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM_ALGORITHM; +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_SIZE; +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_TITLE; +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_VERSION; + +@Slf4j +@Service +@TbCoreComponent +public class DefaultFirmwareStateService implements FirmwareStateService { + + private final FirmwareService firmwareService; + private final DeviceService deviceService; + private final DeviceProfileService deviceProfileService; + private final RuleEngineTelemetryService telemetryService; + + public DefaultFirmwareStateService(FirmwareService firmwareService, DeviceService deviceService, DeviceProfileService deviceProfileService, RuleEngineTelemetryService telemetryService) { + this.firmwareService = firmwareService; + this.deviceService = deviceService; + this.deviceProfileService = deviceProfileService; + this.telemetryService = telemetryService; + } + + @Override + public void update(Device device, boolean created) { + FirmwareId firmwareId = device.getFirmwareId(); + if (firmwareId == null) { + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + firmwareId = deviceProfile.getFirmwareId(); + } + + if (firmwareId == null) { + if (!created) { + remove(device); + } + } else { + update(device, firmwareService.findFirmwareById(device.getTenantId(), firmwareId), System.currentTimeMillis()); + } + } + + @Override + public void update(DeviceProfile deviceProfile) { + TenantId tenantId = deviceProfile.getTenantId(); + + Consumer updateConsumer; + if (deviceProfile.getFirmwareId() != null) { + Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId()); + long ts = System.currentTimeMillis(); + updateConsumer = d -> update(d, firmware, ts); + } else { + updateConsumer = this::remove; + } + + PageLink pageLink = new PageLink(100); + PageData pageData; + do { + //TODO: create a query which will return devices without firmware + pageData = deviceService.findDevicesByTenantIdAndType(tenantId, deviceProfile.getName(), pageLink); + + pageData.getData().stream().filter(d -> d.getFirmwareId() == null).forEach(updateConsumer); + + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); + } + + private void update(Device device, Firmware firmware, long ts) { + TenantId tenantId = device.getTenantId(); + DeviceId deviceId = device.getId(); + + List telemetry = new ArrayList<>(); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion()))); + + telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void tmp) { + log.trace("[{}] Success save telemetry with target firmware for device!", deviceId); + } + + @Override + public void onFailure(Throwable t) { + log.error("[{}] Failed to save telemetry with target firmware for device!", deviceId, t); + } + }); + + List attributes = new ArrayList<>(); + + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_TITLE, firmware.getTitle()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion()))); + + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(FIRMWARE_SIZE, (long) firmware.getData().array().length))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum()))); + telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void tmp) { + log.trace("[{}] Success save attributes with target firmware!", deviceId); + } + + @Override + public void onFailure(Throwable t) { + log.error("[{}] Failed to save attributes with target firmware!", deviceId, t); + } + }); + } + + private void remove(Device device) { + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, + Arrays.asList(FIRMWARE_TITLE, FIRMWARE_VERSION, FIRMWARE_SIZE, FIRMWARE_CHECKSUM_ALGORITHM, FIRMWARE_CHECKSUM), + new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void tmp) { + log.trace("[{}] Success remove target firmware attributes!", device.getId()); + } + + @Override + public void onFailure(Throwable t) { + log.error("[{}] Failed to remove target firmware attributes!", device.getId(), t); + } + }); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java new file mode 100644 index 0000000000..21dc7d8696 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2021 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.firmware; + +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; + +public interface FirmwareStateService { + + void update(Device device, boolean created); + + void update(DeviceProfile deviceProfile); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 39fa30b07f..4f19e1589f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -91,4 +91,19 @@ public class DataConstants { public static final String USERNAME = "username"; public static final String PASSWORD = "password"; + //firmware + //telemetry + public static final String CURRENT_FIRMWARE_TITLE = "cur_fw_title"; + public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version"; + public static final String TARGET_FIRMWARE_TITLE = "target_fw_title"; + public static final String TARGET_FIRMWARE_VERSION = "target_fw_version"; + public static final String CURRENT_FIRMWARE_STATE = "cur_fw_state"; + + //attributes + //telemetry + public static final String FIRMWARE_TITLE = "fw_title"; + public static final String FIRMWARE_VERSION = "fw_version"; + public static final String FIRMWARE_SIZE = "fw_size"; + public static final String FIRMWARE_CHECKSUM = "fw_checksum"; + public static final String FIRMWARE_CHECKSUM_ALGORITHM = "fw_checksum_algorithm"; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index da5abf7549..195f31bd1d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -114,7 +114,8 @@ public class BaseFirmwareService implements FirmwareService { log.trace("Executing findTenantFirmwaresByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validatePageLink(pageLink); - return firmwareInfoDao.findFirmwareInfoByTenantIdAndHasData(tenantId, hasData, pageLink); } + return firmwareInfoDao.findFirmwareInfoByTenantIdAndHasData(tenantId, hasData, pageLink); + } @Override public void deleteFirmware(TenantId tenantId, FirmwareId firmwareId) { @@ -211,31 +212,32 @@ public class BaseFirmwareService implements FirmwareService { throw new DataValidationException("Firmware data should be specified!"); } - if (firmware.getChecksumAlgorithm() != null) { - if (StringUtils.isEmpty(firmware.getChecksum())) { - throw new DataValidationException("Firmware checksum should be specified!"); - } + if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) { + throw new DataValidationException("Firmware checksum algorithm should be specified!"); + } + if (StringUtils.isEmpty(firmware.getChecksum())) { + throw new DataValidationException("Firmware checksum should be specified!"); + } - HashFunction hashFunction; - switch (firmware.getChecksumAlgorithm()) { - case "sha256": - hashFunction = Hashing.sha256(); - break; - case "md5": - hashFunction = Hashing.md5(); - break; - case "crc32": - hashFunction = Hashing.crc32(); - break; - default: - throw new DataValidationException("Unknown checksum algorithm!"); - } + HashFunction hashFunction; + switch (firmware.getChecksumAlgorithm()) { + case "sha256": + hashFunction = Hashing.sha256(); + break; + case "md5": + hashFunction = Hashing.md5(); + break; + case "crc32": + hashFunction = Hashing.crc32(); + break; + default: + throw new DataValidationException("Unknown checksum algorithm!"); + } - String currentChecksum = hashFunction.hashBytes(data.array()).toString(); + String currentChecksum = hashFunction.hashBytes(data.array()).toString(); - if (!currentChecksum.equals(firmware.getChecksum())) { - throw new DataValidationException("Wrong firmware file!"); - } + if (!currentChecksum.equals(firmware.getChecksum())) { + throw new DataValidationException("Wrong firmware file!"); } } From a4347776adeb084323e4ba7e33f685dc6ba94679 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 16 Apr 2021 15:40:44 +0300 Subject: [PATCH 020/127] UI: Refactoring dashboard logo translation --- .../dashboard-page/dashboard-settings-dialog.component.html | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html index 3a20d146dd..244045314b 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.html @@ -71,7 +71,7 @@
- {{ 'dashboard.display-dashboard-toolbar-logo' | translate }} + {{ 'dashboard.display-dashboard-logo' | translate }} Date: Fri, 16 Apr 2021 17:58:49 +0300 Subject: [PATCH 021/127] Fix show dashboard logo in fullscreen mode --- .../components/dashboard-page/dashboard-page.component.html | 2 +- .../home/components/dashboard-page/dashboard-page.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 7344a14310..3f03ea4fff 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -56,7 +56,7 @@ [syncStateWithQueryParam]="syncStateWithQueryParam" [states]="dashboardConfiguration.states"> -
Date: Wed, 31 Mar 2021 22:01:08 +0300 Subject: [PATCH 022/127] queue consumer: implemented nonblocking subscribe method to prevent locks on repartition event. moved message decoding out of the lock to reduce locking time. using monotonic time instead wall-clock. added debug messages --- .../AbstractTbQueueConsumerTemplate.java | 140 ++++++++++-------- .../queue/kafka/TbKafkaConsumerTemplate.java | 3 + 2 files changed, 85 insertions(+), 58 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java index 6bd2a69b2b..43f7321120 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java @@ -21,22 +21,28 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsg; +import javax.annotation.Nonnull; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Queue; import java.util.Set; -import java.util.concurrent.locks.Lock; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; +import static java.util.Collections.emptyList; + @Slf4j public abstract class AbstractTbQueueConsumerTemplate implements TbQueueConsumer { private volatile boolean subscribed; protected volatile boolean stopped = false; protected volatile Set partitions; - protected final Lock consumerLock = new ReentrantLock(); + protected final ReentrantLock consumerLock = new ReentrantLock(); //NonfairSync + final Queue> subscribeQueue = new ConcurrentLinkedQueue<>(); @Getter private final String topic; @@ -47,84 +53,101 @@ public abstract class AbstractTbQueueConsumerTemplate i @Override public void subscribe() { - consumerLock.lock(); - try { - partitions = Collections.singleton(new TopicPartitionInfo(topic, null, null, true)); - subscribed = false; - } finally { - consumerLock.unlock(); + log.info("enqueue topic subscribe {} ", topic); + if (stopped) { + log.error("trying subscribe, but consumer stopped for topic {}", topic); + return; } + subscribeQueue.add(Collections.singleton(new TopicPartitionInfo(topic, null, null, true))); } @Override public void subscribe(Set partitions) { - consumerLock.lock(); - try { - this.partitions = partitions; - subscribed = false; - } finally { - consumerLock.unlock(); + log.info("enqueue topics subscribe {} ", partitions); + if (stopped) { + log.error("trying subscribe, but consumer stopped for topic {}", topic); + return; } + subscribeQueue.add(partitions); } @Override public List poll(long durationInMillis) { + List records; + long startNanos = System.nanoTime(); + if (stopped) { + return errorAndReturnEmpty(); + } if (!subscribed && partitions == null) { - try { - Thread.sleep(durationInMillis); - } catch (InterruptedException e) { - log.debug("Failed to await subscription", e); + return sleepAndReturnEmpty(startNanos, durationInMillis); + } + + if (consumerLock.isLocked()) { + log.error("poll. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock", new RuntimeException("stacktrace")); + } + + consumerLock.lock(); + try { + while (!subscribeQueue.isEmpty()) { + subscribed = false; + partitions = subscribeQueue.poll(); + } + if (!subscribed) { + List topicNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList()); + doSubscribe(topicNames); + subscribed = true; } - } else { - long pollStartTs = System.currentTimeMillis(); - consumerLock.lock(); + records = partitions.isEmpty() ? emptyList() : doPoll(durationInMillis); + } finally { + consumerLock.unlock(); + } + + if (records.isEmpty()) { return sleepAndReturnEmpty(startNanos, durationInMillis); } + + return decodeRecords(records); + } + + @Nonnull + List decodeRecords(@Nonnull List records) { + List result = new ArrayList<>(records.size()); + records.forEach(record -> { try { - if (!subscribed) { - List topicNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList()); - doSubscribe(topicNames); - subscribed = true; + if (record != null) { + result.add(decode(record)); } + } catch (IOException e) { + log.error("Failed decode record: [{}]", record); + throw new RuntimeException("Failed to decode record: ", e); + } + }); + return result; + } - List records; - if (partitions.isEmpty()) { - records = Collections.emptyList(); - } else { - records = doPoll(durationInMillis); - } - if (!records.isEmpty()) { - List result = new ArrayList<>(records.size()); - records.forEach(record -> { - try { - if (record != null) { - result.add(decode(record)); - } - } catch (IOException e) { - log.error("Failed decode record: [{}]", record); - throw new RuntimeException("Failed to decode record: ", e); - } - }); - return result; - } else { - long pollDuration = System.currentTimeMillis() - pollStartTs; - if (pollDuration < durationInMillis) { - try { - Thread.sleep(durationInMillis - pollDuration); - } catch (InterruptedException e) { - if (!stopped) { - log.error("Failed to wait.", e); - } - } - } + List errorAndReturnEmpty() { + log.error("poll invoked but consumer stopped for topic" + topic, new RuntimeException("stacktrace")); + return emptyList(); + } + + List sleepAndReturnEmpty(final long startNanos, final long durationInMillis) { + long durationNanos = TimeUnit.MILLISECONDS.toNanos(durationInMillis); + long spentNanos = System.nanoTime() - startNanos; + if (spentNanos < durationNanos) { + try { + Thread.sleep(Math.max(TimeUnit.NANOSECONDS.toMillis(durationNanos - spentNanos), 1)); + } catch (InterruptedException e) { + if (!stopped) { + log.error("Failed to wait", e); } - } finally { - consumerLock.unlock(); } } - return Collections.emptyList(); + return emptyList(); } @Override public void commit() { + if (consumerLock.isLocked()) { + log.error("commit. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock", new RuntimeException("stacktrace")); + } consumerLock.lock(); try { doCommit(); @@ -135,6 +158,7 @@ public abstract class AbstractTbQueueConsumerTemplate i @Override public void unsubscribe() { + log.info("unsubscribe topic and stop consumer {}", getTopic()); stopped = true; consumerLock.lock(); try { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java index b1b3d5ce05..4429f3518f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java @@ -72,8 +72,10 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue protected void doSubscribe(List topicNames) { if (!topicNames.isEmpty()) { topicNames.forEach(admin::createTopicIfNotExists); + log.info("subscribe topics {}", topicNames); consumer.subscribe(topicNames); } else { + log.info("unsubscribe due to empty topic list"); consumer.unsubscribe(); } } @@ -102,6 +104,7 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue @Override protected void doUnsubscribe() { + log.info("unsubscribe topic and close consumer for topic {}", getTopic()); if (consumer != null) { consumer.unsubscribe(); consumer.close(); From e64b1074a794f348df5d6149c4685001289c8e47 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 31 Mar 2021 22:15:00 +0300 Subject: [PATCH 023/127] queue consumer: added queue check on the very first poll in the lifecycle before any subscribe method calls --- .../server/queue/common/AbstractTbQueueConsumerTemplate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java index 43f7321120..4481e52461 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java @@ -78,7 +78,7 @@ public abstract class AbstractTbQueueConsumerTemplate i if (stopped) { return errorAndReturnEmpty(); } - if (!subscribed && partitions == null) { + if (!subscribed && partitions == null && subscribeQueue.isEmpty()) { return sleepAndReturnEmpty(startNanos, durationInMillis); } From 562637ad340d2f643e149528035f4c7b6a584101 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 1 Apr 2021 11:54:39 +0300 Subject: [PATCH 024/127] ZKDiscoveryService: fix potential outdated server info on recalculatePartitions. info logs added on application event --- .../server/queue/discovery/ZkDiscoveryService.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java index 5253f57248..7e7f280cc9 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/discovery/ZkDiscoveryService.java @@ -125,8 +125,10 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi log.debug("Ignoring application ready event, ZK client is not started, ZK client state [{}]", client.getState()); return; } + log.info("Going to publish current server..."); publishCurrentServer(); - partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); + log.info("Going to recalculate partitions..."); + recalculatePartitions(); } public synchronized void publishCurrentServer() { @@ -281,11 +283,19 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi case CHILD_ADDED: case CHILD_UPDATED: case CHILD_REMOVED: - partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); + recalculatePartitions(); break; default: break; } } + /** + * A single entry point to recalculate partitions + * Synchronized to ensure that other servers info is up to date + * */ + synchronized void recalculatePartitions() { + partitionService.recalculatePartitions(serviceInfoProvider.getServiceInfo(), getOtherServers()); + } + } From 08d9ea302f520c5c941bc096909f7365ccc1c87c Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 16 Apr 2021 16:33:26 +0300 Subject: [PATCH 025/127] logback: logger template added for Top Rule Nodes by max execution time --- application/src/main/resources/logback.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml index f8085ec985..caf78e1537 100644 --- a/application/src/main/resources/logback.xml +++ b/application/src/main/resources/logback.xml @@ -37,6 +37,9 @@ + + + From 3228312cdb66bbbe35ca1494de83bae384bcbc13 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 12 Apr 2021 16:48:20 +0300 Subject: [PATCH 026/127] alarms count: performance fix based on pg_stat analysis. Counters queries refactored as two subqueries: inner join (count with relations) + left 'anti' join (count without relations). AlarmServiceSqlTest passed. CE --- .../server/dao/sql/alarm/AlarmRepository.java | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index b422f1cd4a..446125e4fb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java @@ -50,23 +50,41 @@ public interface AlarmRepository extends CrudRepository { "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + - "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" + - "OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" + - "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))", - countQuery = "SELECT count(a) FROM AlarmEntity a " + - "LEFT JOIN RelationEntity re ON a.id = re.toId " + + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " + + " OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " + + " OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) " + , + countQuery = "" + + "SELECT count(a) + " + //alarms with relations only + " (SELECT count(a) FROM AlarmEntity a " + //alarms WITHOUT any relations + " LEFT JOIN RelationEntity re ON a.id = re.toId " + + " AND re.relationTypeGroup = 'ALARM' " + + " AND re.toType = 'ALARM' " + + " AND re.fromId = :affectedEntityId " + + " AND re.fromType = :affectedEntityType " + + " WHERE a.tenantId = :tenantId " + + " AND (a.originatorId = :affectedEntityId) " + + " AND (re.fromId IS NULL) " + //anti join + " AND (:startTime IS NULL OR a.createdTime >= :startTime) " + + " AND (:endTime IS NULL OR a.createdTime <= :endTime) " + + " AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + + " AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " + + " OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " + + " OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) " + + " )" + + "FROM AlarmEntity a " + + "INNER JOIN RelationEntity re ON a.id = re.toId " + "AND re.relationTypeGroup = 'ALARM' " + "AND re.toType = 'ALARM' " + "AND re.fromId = :affectedEntityId " + "AND re.fromType = :affectedEntityType " + "WHERE a.tenantId = :tenantId " + - "AND (a.originatorId = :affectedEntityId or re.fromId IS NOT NULL) " + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + - "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" + - "OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" + - "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))") + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " + + " OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " + + " OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) ") Page findAlarms(@Param("tenantId") UUID tenantId, @Param("affectedEntityId") UUID affectedEntityId, @Param("affectedEntityType") String affectedEntityType, From b8291be084bc2b843f61842194c82ff90d157239 Mon Sep 17 00:00:00 2001 From: Valerii Sosliuk Date: Fri, 16 Apr 2021 17:52:38 +0300 Subject: [PATCH 027/127] Update Latest TS by timestamp There is a bug when the historic data arrives and overrides the value with morerecent timestamp in ts_kv_latest table. This PR adds possibility to update the ts_kv_latest table only if the value that arrives has the newer timestamp than the one that is already in ts_kv_latest --- .../src/main/resources/thingsboard.yml | 1 + .../psql/PsqlLatestInsertTsRepository.java | 27 ++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 5ba575a413..a9234a0abb 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -243,6 +243,7 @@ sql: batch_max_delay: "${SQL_TS_LATEST_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_TS_LATEST_BATCH_STATS_PRINT_MS:10000}" batch_threads: "${SQL_TS_LATEST_BATCH_THREADS:4}" + update_by_latest_ts: "${SQL_TS_UPDATE_BY_LATEST_TIMESTAMP:true}" # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks batch_sort: "${SQL_BATCH_SORT:false}" # Specify whether to remove null characters from strValue of attributes and timeseries before insert diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java index 7a056f4872..d97bc1c051 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.sqlts.insert.latest.psql; +import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.stereotype.Repository; import org.springframework.transaction.TransactionStatus; @@ -37,20 +38,34 @@ import java.util.List; @Transactional public class PsqlLatestInsertTsRepository extends AbstractInsertRepository implements InsertLatestTsRepository { + @Value("${sql.ts_latest.update_by_latest_ts:true}") + private Boolean updateByLatestTs; + private static final String BATCH_UPDATE = - "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE entity_id = ? and key = ?"; + "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE entity_id = ? AND key = ?;"; private static final String INSERT_OR_UPDATE = "INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);"; + private static final String BATCH_UPDATE_BY_LATEST_TS = + "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE entity_id = ? AND key = ? AND ts <= ?;"; + + + private static final String INSERT_OR_UPDATE_BY_LATEST_TS = + "INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + + "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE ts_kv_latest.ts <= ?;"; + @Override public void saveOrUpdate(List entities) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { - int[] result = jdbcTemplate.batchUpdate(BATCH_UPDATE, new BatchPreparedStatementSetter() { + String batchUpdateQuery = updateByLatestTs ? BATCH_UPDATE_BY_LATEST_TS : BATCH_UPDATE; + String insertOrUpdateQuery = updateByLatestTs ? INSERT_OR_UPDATE_BY_LATEST_TS : INSERT_OR_UPDATE; + + int[] result = jdbcTemplate.batchUpdate(batchUpdateQuery, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { TsKvLatestEntity tsKvLatestEntity = entities.get(i); @@ -80,6 +95,9 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple ps.setObject(7, tsKvLatestEntity.getEntityId()); ps.setInt(8, tsKvLatestEntity.getKey()); + if (updateByLatestTs) { + ps.setLong(9, tsKvLatestEntity.getTs()); + } } @Override @@ -102,7 +120,7 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple } } - jdbcTemplate.batchUpdate(INSERT_OR_UPDATE, new BatchPreparedStatementSetter() { + jdbcTemplate.batchUpdate(insertOrUpdateQuery, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { TsKvLatestEntity tsKvLatestEntity = insertEntities.get(i); @@ -111,6 +129,9 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple ps.setLong(3, tsKvLatestEntity.getTs()); ps.setLong(9, tsKvLatestEntity.getTs()); + if (updateByLatestTs) { + ps.setLong(15, tsKvLatestEntity.getTs()); + } if (tsKvLatestEntity.getBooleanValue() != null) { ps.setBoolean(4, tsKvLatestEntity.getBooleanValue()); From e7fd07652e085dc9bfcd945bc9a7a2404aeb41fc Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Apr 2021 13:16:55 +0300 Subject: [PATCH 028/127] Removed code duplicates and fixed one minor issue --- .../latest/psql/PsqlLatestInsertTsRepository.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java index d97bc1c051..5b18fde202 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/latest/psql/PsqlLatestInsertTsRepository.java @@ -42,20 +42,15 @@ public class PsqlLatestInsertTsRepository extends AbstractInsertRepository imple private Boolean updateByLatestTs; private static final String BATCH_UPDATE = - "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE entity_id = ? AND key = ?;"; - + "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE entity_id = ? AND key = ?"; private static final String INSERT_OR_UPDATE = "INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + - "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json);"; - - private static final String BATCH_UPDATE_BY_LATEST_TS = - "UPDATE ts_kv_latest SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE entity_id = ? AND key = ? AND ts <= ?;"; + "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json)"; + private static final String BATCH_UPDATE_BY_LATEST_TS = BATCH_UPDATE + " AND ts_kv_latest.ts <= ?"; - private static final String INSERT_OR_UPDATE_BY_LATEST_TS = - "INSERT INTO ts_kv_latest (entity_id, key, ts, bool_v, str_v, long_v, dbl_v, json_v) VALUES(?, ?, ?, ?, ?, ?, ?, cast(? AS json)) " + - "ON CONFLICT (entity_id, key) DO UPDATE SET ts = ?, bool_v = ?, str_v = ?, long_v = ?, dbl_v = ?, json_v = cast(? AS json) WHERE ts_kv_latest.ts <= ?;"; + private static final String INSERT_OR_UPDATE_BY_LATEST_TS = INSERT_OR_UPDATE + " WHERE ts_kv_latest.ts <= ?"; @Override public void saveOrUpdate(List entities) { From 27788790df30fc349d34073614f5b05ff01ae5ca Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 16 Mar 2021 18:15:04 +0200 Subject: [PATCH 029/127] dependency upgrade: grpc.version up to 1.36.0. explicitly added protobuf-java-util due to grpc not depends on it anymore --- common/queue/pom.xml | 4 ++++ pom.xml | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/common/queue/pom.xml b/common/queue/pom.xml index e0b30dd654..31ec44e8f7 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -112,6 +112,10 @@ com.google.protobuf protobuf-java + + com.google.protobuf + protobuf-java-util + org.apache.curator curator-recipes diff --git a/pom.xml b/pom.xml index d954128e96..a44ade17de 100755 --- a/pom.xml +++ b/pom.xml @@ -77,7 +77,7 @@ 4.2.0 3.5.5 3.11.4 - 1.22.1 + 1.36.0 1.18.18 1.2.4 4.1.60.Final @@ -1301,6 +1301,11 @@ protobuf-java ${protobuf.version} + + com.google.protobuf + protobuf-java-util + ${protobuf.version} + io.grpc grpc-netty From 8b653d7065271c2392a7a5fcd648fc9be06064ae Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 11 Mar 2021 13:18:58 +0200 Subject: [PATCH 030/127] rule chain. implemented save/update/delete @Transactional. Added sync DAO methods to run in transaction. Moved loadRuleChainMetaData outside of saveRuleChainMetaData transaction --- .../controller/RuleChainController.java | 3 +- .../server/dao/rule/RuleChainService.java | 2 +- .../dao/relation/BaseRelationService.java | 50 ++++++++++++----- .../server/dao/relation/RelationDao.java | 8 ++- .../server/dao/rule/BaseRuleChainService.java | 54 ++++++++----------- .../dao/sql/relation/JpaRelationDao.java | 22 +++++--- .../dao/service/BaseRuleChainServiceTest.java | 6 ++- 7 files changed, 90 insertions(+), 55 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 85ec0950e3..d5fcc2706f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -244,7 +244,8 @@ public class RuleChainController extends BaseController { } RuleChain ruleChain = checkRuleChain(ruleChainMetaData.getRuleChainId(), Operation.WRITE); - RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData)); + checkNotNull(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData) ? true : null); + RuleChainMetaData savedRuleChainMetaData = checkNotNull(ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId())); if (RuleChainType.CORE.equals(ruleChain.getType())) { tbClusterService.onEntityStateChange(ruleChain.getTenantId(), ruleChain.getId(), ComponentLifecycleEvent.UPDATED); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java index 086d954e27..5740abb354 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rule/RuleChainService.java @@ -43,7 +43,7 @@ public interface RuleChainService { boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId); - RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData); + boolean saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData); RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 44b328fd9f..15ddb9a390 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -179,11 +179,28 @@ public class BaseRelationService implements RelationService { @Override public void deleteEntityRelations(TenantId tenantId, EntityId entityId) { - try { - deleteEntityRelationsAsync(tenantId, entityId).get(); - } catch (InterruptedException | ExecutionException e) { - throw new RuntimeException(e); + log.trace("Executing deleteEntityRelations [{}]", entityId); + validate(entityId); + final Cache cache = cacheManager.getCache(RELATIONS_CACHE); + List inboundRelations = new ArrayList<>(); + for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { + inboundRelations.addAll(relationDao.findAllByTo(tenantId, entityId, typeGroup)); + } + + List outboundRelations = new ArrayList<>(); + for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { + outboundRelations.addAll(relationDao.findAllByFrom(tenantId, entityId, typeGroup)); + } + + for (EntityRelation relation : inboundRelations){ + delete(tenantId, cache, relation, true); } + + for (EntityRelation relation : outboundRelations){ + delete(tenantId, cache, relation, false); + } + + relationDao.deleteOutboundRelations(tenantId, entityId); } @Override @@ -193,14 +210,14 @@ public class BaseRelationService implements RelationService { validate(entityId); List>> inboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - inboundRelationsList.add(relationDao.findAllByTo(tenantId, entityId, typeGroup)); + inboundRelationsList.add(relationDao.findAllByToAsync(tenantId, entityId, typeGroup)); } ListenableFuture>> inboundRelations = Futures.allAsList(inboundRelationsList); List>> outboundRelationsList = new ArrayList<>(); for (RelationTypeGroup typeGroup : RelationTypeGroup.values()) { - outboundRelationsList.add(relationDao.findAllByFrom(tenantId, entityId, typeGroup)); + outboundRelationsList.add(relationDao.findAllByFromAsync(tenantId, entityId, typeGroup)); } ListenableFuture>> outboundRelations = Futures.allAsList(outboundRelationsList); @@ -242,6 +259,15 @@ public class BaseRelationService implements RelationService { } } + boolean delete(TenantId tenantId, Cache cache, EntityRelation relation, boolean deleteFromDb) { + cacheEviction(relation, cache); + if (deleteFromDb) { + return relationDao.deleteRelation(tenantId, relation); + } else { + return false; + } + } + private void cacheEviction(EntityRelation relation, Cache cache) { List fromToTypeAndTypeGroup = new ArrayList<>(); fromToTypeAndTypeGroup.add(relation.getFrom()); @@ -283,7 +309,7 @@ public class BaseRelationService implements RelationService { validate(from); validateTypeGroup(typeGroup); try { - return relationDao.findAllByFrom(tenantId, from, typeGroup).get(); + return relationDao.findAllByFromAsync(tenantId, from, typeGroup).get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } @@ -306,7 +332,7 @@ public class BaseRelationService implements RelationService { if (fromCache != null) { return Futures.immediateFuture(fromCache); } else { - ListenableFuture> relationsFuture = relationDao.findAllByFrom(tenantId, from, typeGroup); + ListenableFuture> relationsFuture = relationDao.findAllByFromAsync(tenantId, from, typeGroup); Futures.addCallback(relationsFuture, new FutureCallback>() { @Override @@ -327,7 +353,7 @@ public class BaseRelationService implements RelationService { log.trace("Executing findInfoByFrom [{}][{}]", from, typeGroup); validate(from); validateTypeGroup(typeGroup); - ListenableFuture> relations = relationDao.findAllByFrom(tenantId, from, typeGroup); + ListenableFuture> relations = relationDao.findAllByFromAsync(tenantId, from, typeGroup); return Futures.transformAsync(relations, relations1 -> { List> futures = new ArrayList<>(); @@ -365,7 +391,7 @@ public class BaseRelationService implements RelationService { validate(to); validateTypeGroup(typeGroup); try { - return relationDao.findAllByTo(tenantId, to, typeGroup).get(); + return relationDao.findAllByToAsync(tenantId, to, typeGroup).get(); } catch (InterruptedException | ExecutionException e) { throw new RuntimeException(e); } @@ -388,7 +414,7 @@ public class BaseRelationService implements RelationService { if (fromCache != null) { return Futures.immediateFuture(fromCache); } else { - ListenableFuture> relationsFuture = relationDao.findAllByTo(tenantId, to, typeGroup); + ListenableFuture> relationsFuture = relationDao.findAllByToAsync(tenantId, to, typeGroup); Futures.addCallback(relationsFuture, new FutureCallback>() { @Override @@ -409,7 +435,7 @@ public class BaseRelationService implements RelationService { log.trace("Executing findInfoByTo [{}][{}]", to, typeGroup); validate(to); validateTypeGroup(typeGroup); - ListenableFuture> relations = relationDao.findAllByTo(tenantId, to, typeGroup); + ListenableFuture> relations = relationDao.findAllByToAsync(tenantId, to, typeGroup); return Futures.transformAsync(relations, relations1 -> { List> futures = new ArrayList<>(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java index a8c4d9bbc2..d60f176832 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/RelationDao.java @@ -31,11 +31,15 @@ import java.util.List; */ public interface RelationDao { - ListenableFuture> findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); + ListenableFuture> findAllByFromAsync(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); + + List findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup); ListenableFuture> findAllByFromAndType(TenantId tenantId, EntityId from, String relationType, RelationTypeGroup typeGroup); - ListenableFuture> findAllByTo(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup); + ListenableFuture> findAllByToAsync(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup); + + List findAllByTo(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup); ListenableFuture> findAllByToAndType(TenantId tenantId, EntityId to, String relationType, RelationTypeGroup typeGroup); diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index b85a257ed2..baa3ae8272 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -26,6 +26,7 @@ import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; @@ -96,23 +97,19 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC private TbTenantProfileCache tenantProfileCache; @Override + @Transactional public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); RuleChain savedRuleChain = ruleChainDao.save(ruleChain.getTenantId(), ruleChain); if (ruleChain.isRoot() && ruleChain.getId() == null) { - try { - createRelation(ruleChain.getTenantId(), new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); - } catch (Exception e) { - log.warn("[{}] Failed to create tenant to root rule chain relation. from: [{}], to: [{}]", - savedRuleChain.getTenantId(), savedRuleChain.getTenantId(), savedRuleChain.getId(), e); - throw new RuntimeException(e); - } + createRelation(ruleChain.getTenantId(), new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); } return savedRuleChain; } @Override + @Transactional public boolean setRootRuleChain(TenantId tenantId, RuleChainId ruleChainId) { RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId()); if (!ruleChain.isRoot()) { @@ -145,11 +142,12 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } @Override - public RuleChainMetaData saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) { + @Transactional + public boolean saveRuleChainMetaData(TenantId tenantId, RuleChainMetaData ruleChainMetaData) { Validator.validateId(ruleChainMetaData.getRuleChainId(), "Incorrect rule chain id."); RuleChain ruleChain = findRuleChainById(tenantId, ruleChainMetaData.getRuleChainId()); if (ruleChain == null) { - return null; + return false; } if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) { @@ -184,14 +182,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC for (RuleNode node : toAddOrUpdate) { node.setRuleChainId(ruleChain.getId()); RuleNode savedNode = ruleNodeDao.save(tenantId, node); - try { - createRelation(tenantId, new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); - } catch (Exception e) { - log.warn("[{}] Failed to create rule chain to rule node relation. from: [{}], to: [{}]", - ruleChainMetaData.getRuleChainId(), savedNode.getId()); - throw new RuntimeException(e); - } + createRelation(tenantId, new EntityRelation(ruleChainMetaData.getRuleChainId(), savedNode.getId(), + EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); int index = nodes.indexOf(node); nodes.set(index, savedNode); ruleNodeIndexMap.put(savedNode.getId(), index); @@ -213,12 +205,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC EntityId from = nodes.get(nodeConnection.getFromIndex()).getId(); EntityId to = nodes.get(nodeConnection.getToIndex()).getId(); String type = nodeConnection.getType(); - try { - createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); - } catch (Exception e) { - log.warn("[{}] Failed to create rule node relation. from: [{}], to: [{}]", from, to); - throw new RuntimeException(e); - } + createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); } } if (ruleChainMetaData.getRuleChainConnections() != null) { @@ -226,16 +213,11 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); String type = nodeToRuleChainConnection.getType(); - try { - createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); - } catch (Exception e) { - log.warn("[{}] Failed to create rule node to rule chain relation. from: [{}], to: [{}]", from, to); - throw new RuntimeException(e); - } + createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); } } - return loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()); + return true; } private void validateCircles(List connectionInfos) { @@ -410,6 +392,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } @Override + @Transactional public void deleteRuleChainById(TenantId tenantId, RuleChainId ruleChainId) { Validator.validateId(ruleChainId, "Incorrect rule chain id for delete request."); RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId()); @@ -716,6 +699,15 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ruleNodeDao.removeById(tenantId, entityId.getId()); } + private void createRelation(TenantId tenantId, EntityRelation relation) { + log.debug("Creating relation: {}", relation); + relationService.saveRelation(tenantId, relation); + } + + private void deleteRelation(TenantId tenantId, EntityRelation relation) { + log.debug("Deleting relation: {}", relation); + relationService.deleteRelation(tenantId, relation); + } private DataValidator ruleChainValidator = new DataValidator() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java index 89b63b87cf..5e10cabda2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java @@ -49,12 +49,17 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple private RelationInsertRepository relationInsertRepository; @Override - public ListenableFuture> findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) { - return service.submit(() -> DaoUtil.convertDataList( + public ListenableFuture> findAllByFromAsync(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) { + return service.submit(() -> findAllByFrom(tenantId, from, typeGroup)); + } + + @Override + public List findAllByFrom(TenantId tenantId, EntityId from, RelationTypeGroup typeGroup) { + return DaoUtil.convertDataList( relationRepository.findAllByFromIdAndFromTypeAndRelationTypeGroup( from.getId(), from.getEntityType().name(), - typeGroup.name()))); + typeGroup.name())); } @Override @@ -68,12 +73,17 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple } @Override - public ListenableFuture> findAllByTo(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup) { - return service.submit(() -> DaoUtil.convertDataList( + public ListenableFuture> findAllByToAsync(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup) { + return service.submit(() -> findAllByTo(tenantId, to, typeGroup)); + } + + @Override + public List findAllByTo(TenantId tenantId, EntityId to, RelationTypeGroup typeGroup) { + return DaoUtil.convertDataList( relationRepository.findAllByToIdAndToTypeAndRelationTypeGroup( to.getId(), to.getEntityType().name(), - typeGroup.name()))); + typeGroup.name())); } @Override diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java index e7cd349e73..3f4b103ce2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java @@ -292,7 +292,8 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { ruleNodes.set(name3Index, ruleNode4); - RuleChainMetaData updatedRuleChainMetaData = ruleChainService.saveRuleChainMetaData(tenantId, savedRuleChainMetaData); + Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, savedRuleChainMetaData)); + RuleChainMetaData updatedRuleChainMetaData = ruleChainService.loadRuleChainMetaData(tenantId, savedRuleChainMetaData.getRuleChainId()); Assert.assertEquals(3, updatedRuleChainMetaData.getNodes().size()); Assert.assertEquals(3, updatedRuleChainMetaData.getConnections().size()); @@ -404,7 +405,8 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { ruleChainMetaData.addConnectionInfo(0,2,"fail"); ruleChainMetaData.addConnectionInfo(1,2,"success"); - return ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData); + Assert.assertTrue(ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData)); + return ruleChainService.loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()); } private RuleChainMetaData createRuleChainMetadataWithCirclingRelation() throws Exception { From e17ba0531d55e979d40ec61c72ac0f73cfcd8542 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 16 Apr 2021 18:50:25 +0300 Subject: [PATCH 031/127] rule chain: fixed conflict after cherry-pick. removed createRelation and deleteRelation. It moved to the AbstractEntityService before --- .../server/dao/rule/BaseRuleChainService.java | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index baa3ae8272..786d86d8dd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -699,16 +699,6 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ruleNodeDao.removeById(tenantId, entityId.getId()); } - private void createRelation(TenantId tenantId, EntityRelation relation) { - log.debug("Creating relation: {}", relation); - relationService.saveRelation(tenantId, relation); - } - - private void deleteRelation(TenantId tenantId, EntityRelation relation) { - log.debug("Deleting relation: {}", relation); - relationService.deleteRelation(tenantId, relation); - } - private DataValidator ruleChainValidator = new DataValidator() { @Override From a100d07daf0db81ee28dc63ad6b4ef36e7e08ad8 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Tue, 13 Apr 2021 19:36:35 +0300 Subject: [PATCH 032/127] Fixed incorrect RestClient method getAlarms --- .../thingsboard/rest/client/RestClient.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 815897faaa..0e04cc132e 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -305,17 +305,25 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId()); } - public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) { + public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, String offset, Boolean fetchOriginator) { + String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}&"; Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); - params.put("searchStatus", searchStatus.name()); - params.put("status", status.name()); + if(searchStatus != null) { + params.put("searchStatus", searchStatus.name()); + urlSecondPart += "searchStatus={searchStatus}&"; + } + if(status != null) { + params.put("status", status.name()); + urlSecondPart += "status={status}&"; + } params.put("fetchOriginator", String.valueOf(fetchOriginator)); + params.put("offset", offset); addTimePageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/alarm/{entityType}/{entityId}?searchStatus={searchStatus}&status={status}&fetchOriginator={fetchOriginator}&" + getTimeUrlParams(pageLink), + baseURL + urlSecondPart + getUrlParams(pageLink) + getTimeUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -2841,7 +2849,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } private String getTimeUrlParams(TimePageLink pageLink) { - String urlParams = "limit={limit}&ascOrder={ascOrder}"; + String urlParams = ""; if (pageLink.getStartTime() != null) { urlParams += "&startTime={startTime}"; } From fad1040f249a74e2be3d4fcf63b6def20736230f Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 14 Apr 2021 13:26:30 +0300 Subject: [PATCH 033/127] Refactoring: offset added to request URL --- .../main/java/org/thingsboard/rest/client/RestClient.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 0e04cc132e..e39284c547 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -127,6 +127,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -305,7 +306,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId()); } - public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, String offset, Boolean fetchOriginator) { + public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, UUID offset, Boolean fetchOriginator) { String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}&"; Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); @@ -319,7 +320,10 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { urlSecondPart += "status={status}&"; } params.put("fetchOriginator", String.valueOf(fetchOriginator)); - params.put("offset", offset); + if(offset != null) { + params.put("offset", offset.toString()); + urlSecondPart += "offset={offset}&"; + } addTimePageLinkToParam(params, pageLink); return restTemplate.exchange( From 5d64775f675defa240308846c2c173129cec1f5d Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 14 Apr 2021 17:10:50 +0300 Subject: [PATCH 034/127] Correct call of dependent methods --- .../src/main/java/org/thingsboard/rest/client/RestClient.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index e39284c547..79b03d2958 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -327,7 +327,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { addTimePageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + urlSecondPart + getUrlParams(pageLink) + getTimeUrlParams(pageLink), + baseURL + urlSecondPart + getTimeUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { @@ -2853,7 +2853,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } private String getTimeUrlParams(TimePageLink pageLink) { - String urlParams = ""; + String urlParams = getUrlParams(pageLink); if (pageLink.getStartTime() != null) { urlParams += "&startTime={startTime}"; } From e3caf24a9b337805854d167b01535ff42023d573 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Fri, 16 Apr 2021 20:57:05 +0300 Subject: [PATCH 035/127] Removed redundant parameter for AlarmController, AlarmQuery and RestClient.getAlarms --- .../server/controller/AlarmController.java | 10 ++-------- .../server/common/data/alarm/AlarmQuery.java | 4 ---- .../java/org/thingsboard/rest/client/RestClient.java | 11 +++-------- 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 3f32e878f4..9453bd46b7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -46,8 +46,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; -import java.util.UUID; - @RestController @TbCoreComponent @RequestMapping("/api") @@ -177,7 +175,6 @@ public class AlarmController extends BaseController { @RequestParam(required = false) String sortOrder, @RequestParam(required = false) Long startTime, @RequestParam(required = false) Long endTime, - @RequestParam(required = false) String offset, @RequestParam(required = false) Boolean fetchOriginator ) throws ThingsboardException { checkParameter("EntityId", strEntityId); @@ -191,12 +188,9 @@ public class AlarmController extends BaseController { } checkEntityId(entityId, Operation.READ); TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); - UUID idOffsetUuid = null; - if (StringUtils.isNotEmpty(offset)) { - idOffsetUuid = toUUID(offset); - } + try { - return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator, idOffsetUuid)).get()); + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); } catch (Exception e) { throw handleException(e); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java index 91bd902e9a..80e3730955 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmQuery.java @@ -19,11 +19,8 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.TimePageLink; -import java.util.UUID; - /** * Created by ashvayka on 11.05.17. */ @@ -37,6 +34,5 @@ public class AlarmQuery { private AlarmSearchStatus searchStatus; private AlarmStatus status; private Boolean fetchOriginator; - private UUID idOffset; } diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 79b03d2958..0b48487a63 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -121,13 +121,11 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle; import java.io.Closeable; import java.io.IOException; import java.net.URI; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -306,11 +304,12 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId()); } - public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, UUID offset, Boolean fetchOriginator) { + public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) { String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}&"; Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); + params.put("fetchOriginator", String.valueOf(fetchOriginator)); if(searchStatus != null) { params.put("searchStatus", searchStatus.name()); urlSecondPart += "searchStatus={searchStatus}&"; @@ -319,11 +318,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params.put("status", status.name()); urlSecondPart += "status={status}&"; } - params.put("fetchOriginator", String.valueOf(fetchOriginator)); - if(offset != null) { - params.put("offset", offset.toString()); - urlSecondPart += "offset={offset}&"; - } + addTimePageLinkToParam(params, pageLink); return restTemplate.exchange( From 91e0a77afd5d34ab324681ceee3db2c164cebd37 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Mon, 19 Apr 2021 15:36:38 +0300 Subject: [PATCH 036/127] Fixed incorrect adding of additional parameters to URL in getAlarms method --- .../main/java/org/thingsboard/rest/client/RestClient.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 0b48487a63..e51117c080 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -305,18 +305,18 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public PageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) { - String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}&"; + String urlSecondPart = "/api/alarm/{entityType}/{entityId}?fetchOriginator={fetchOriginator}"; Map params = new HashMap<>(); params.put("entityType", entityId.getEntityType().name()); params.put("entityId", entityId.getId().toString()); params.put("fetchOriginator", String.valueOf(fetchOriginator)); if(searchStatus != null) { params.put("searchStatus", searchStatus.name()); - urlSecondPart += "searchStatus={searchStatus}&"; + urlSecondPart += "&searchStatus={searchStatus}"; } if(status != null) { params.put("status", status.name()); - urlSecondPart += "status={status}&"; + urlSecondPart += "&status={status}"; } addTimePageLinkToParam(params, pageLink); From cb9450104ebdaf9a440cd763d0cd3562c3d926a0 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 19 Apr 2021 20:16:55 +0300 Subject: [PATCH 037/127] device count performance for 100k+ devices per tenant. Added additional tests for the DeviceService --- .../server/dao/device/DeviceService.java | 2 + .../DefaultTenantProfileConfiguration.java | 6 + .../server/dao/device/DeviceServiceImpl.java | 5 + .../dao/sql/device/DeviceRepository.java | 13 +- .../dao/service/AbstractServiceTest.java | 11 ++ .../dao/service/BaseDeviceServiceTest.java | 183 ++++++++++++------ 6 files changed, 160 insertions(+), 60 deletions(-) diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index 06a2fe4e7d..b3cfb66a47 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -98,4 +98,6 @@ public interface DeviceService { PageData findDevicesByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink); PageData findDevicesByTenantIdAndEdgeIdAndType(TenantId tenantId, EdgeId edgeId, String type, PageLink pageLink); + + long countByTenantId(TenantId tenantId); } 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 fc63133510..72713f12d3 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 @@ -15,10 +15,16 @@ */ package org.thingsboard.server.common.data.tenant.profile; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.TenantProfileType; +@AllArgsConstructor +@NoArgsConstructor +@Builder @Data public class DefaultTenantProfileConfiguration implements TenantProfileConfiguration { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index d8f2444cb5..9d7fa59ad8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -615,6 +615,11 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndEdgeIdAndType(tenantId.getId(), edgeId.getId(), type, pageLink); } + @Override + public long countByTenantId(TenantId tenantId) { + return deviceDao.countByTenantId(tenantId); + } + private DataValidator deviceValidator = new DataValidator() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 9f16397a65..0de59ac8be 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -189,5 +189,16 @@ public interface DeviceRepository extends PagingAndSortingRepository + * There is two way to count devices. + * OPTIMAL: count(*) + * - returns _row_count_ and use index-only scan (super fast). + * SLOW: count(id) + * - returns _NON_NULL_id_count and performs table scan to verify isNull for each id in filtered rows. + * */ + @Query("SELECT count(*) FROM DeviceEntity d WHERE d.tenantId = :tenantId") + Long countByTenantId(@Param("tenantId") UUID tenantId); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index 0761f28d33..327b05ee22 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Event; +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; @@ -70,6 +71,8 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; +import static org.junit.Assert.assertNotNull; + @RunWith(SpringRunner.class) @ContextConfiguration(classes = AbstractServiceTest.class, loader = AnnotationConfigContextLoader.class) @@ -225,4 +228,12 @@ public abstract class AbstractServiceTest { return deviceProfile; } + public TenantId createTenant() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant " + Uuids.timeBased()); + Tenant savedTenant = tenantService.saveTenant(tenant); + assertNotNull(savedTenant); + return savedTenant.getId(); + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index b323d0088c..16f7ab2868 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -21,13 +21,19 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.*; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; @@ -37,33 +43,93 @@ import java.util.List; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; public abstract class BaseDeviceServiceTest extends AbstractServiceTest { - + private IdComparator idComparator = new IdComparator<>(); - + private TenantId tenantId; + private TenantId anotherTenantId; @Before public void before() { - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - Tenant savedTenant = tenantService.saveTenant(tenant); - Assert.assertNotNull(savedTenant); - tenantId = savedTenant.getId(); + tenantId = createTenant(); + anotherTenantId = createTenant(); } @After public void after() { tenantService.deleteTenant(tenantId); + tenantService.deleteTenant(anotherTenantId); + + tenantProfileService.deleteTenantProfiles(tenantId); + tenantProfileService.deleteTenantProfiles(anotherTenantId); + } + + @Test + public void testSaveDevicesWithoutMaxDeviceLimit() { + Device device = this.saveDevice(tenantId, "My device"); + deleteDevice(tenantId, device); } @Test - public void testSaveDevice() { + public void testSaveDevicesWithInfiniteMaxDeviceLimit() { + TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId); + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxDevices(Long.MAX_VALUE).build()); + tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile); + + Device device = this.saveDevice(tenantId, "My device"); + deleteDevice(tenantId, device); + } + + @Test(expected = DataValidationException.class) + public void testSaveDevicesWithMaxDeviceOutOfLimit() { + TenantProfile defaultTenantProfile = tenantProfileService.findDefaultTenantProfile(tenantId); + defaultTenantProfile.getProfileData().setConfiguration(DefaultTenantProfileConfiguration.builder().maxDevices(1).build()); + tenantProfileService.saveTenantProfile(tenantId, defaultTenantProfile); + + Assert.assertEquals(0, deviceService.countByTenantId(tenantId)); + + this.saveDevice(tenantId, "My first device"); + Assert.assertEquals(1, deviceService.countByTenantId(tenantId)); + + this.saveDevice(tenantId, "My second device that out of maxDeviceCount limit"); + } + + @Test + public void testCountByTenantId() { + Assert.assertEquals(0, deviceService.countByTenantId(tenantId)); + Assert.assertEquals(0, deviceService.countByTenantId(anotherTenantId)); + Assert.assertEquals(0, deviceService.countByTenantId(TenantId.SYS_TENANT_ID)); + + Device anotherDevice = this.saveDevice(anotherTenantId, "My device 1"); + Assert.assertEquals(1, deviceService.countByTenantId(anotherTenantId)); + + int maxDevices = 8; + List devices = new ArrayList<>(maxDevices); + + for (int i = 1; i <= maxDevices; i++) { + devices.add(this.saveDevice(tenantId, "My device " + i)); + Assert.assertEquals(i, deviceService.countByTenantId(tenantId)); + } + + Assert.assertEquals(maxDevices, deviceService.countByTenantId(tenantId)); + Assert.assertEquals(1, deviceService.countByTenantId(anotherTenantId)); + Assert.assertEquals(0, deviceService.countByTenantId(TenantId.SYS_TENANT_ID)); + + devices.forEach(device -> deleteDevice(tenantId, device)); + deleteDevice(anotherTenantId, anotherDevice); + } + + void deleteDevice(TenantId tenantId, Device device) { + deviceService.deleteDevice(tenantId, device.getId()); + } + + Device saveDevice(TenantId tenantId, final String name) { Device device = new Device(); device.setTenantId(tenantId); - device.setName("My device"); + device.setName(name); device.setType("default"); Device savedDevice = deviceService.saveDevice(device); - + Assert.assertNotNull(savedDevice); Assert.assertNotNull(savedDevice.getId()); Assert.assertTrue(savedDevice.getCreatedTime() > 0); @@ -71,7 +137,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertNotNull(savedDevice.getCustomerId()); Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId()); Assert.assertEquals(device.getName(), savedDevice.getName()); - + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); Assert.assertNotNull(deviceCredentials); Assert.assertNotNull(deviceCredentials.getId()); @@ -79,16 +145,15 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, deviceCredentials.getCredentialsType()); Assert.assertNotNull(deviceCredentials.getCredentialsId()); Assert.assertEquals(20, deviceCredentials.getCredentialsId().length()); - - savedDevice.setName("My new device"); - + + savedDevice.setName("New " + savedDevice.getName()); + deviceService.saveDevice(savedDevice); Device foundDevice = deviceService.findDeviceById(tenantId, savedDevice.getId()); Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); - - deviceService.deleteDevice(tenantId, savedDevice.getId()); + return foundDevice; } - + @Test(expected = DataValidationException.class) public void testSaveDeviceWithEmptyName() { Device device = new Device(); @@ -96,7 +161,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { device.setTenantId(tenantId); deviceService.saveDevice(device); } - + @Test(expected = DataValidationException.class) public void testSaveDeviceWithEmptyTenant() { Device device = new Device(); @@ -104,7 +169,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { device.setType("default"); deviceService.saveDevice(device); } - + @Test(expected = DataValidationException.class) public void testSaveDeviceWithInvalidTenant() { Device device = new Device(); @@ -113,7 +178,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { device.setTenantId(new TenantId(Uuids.timeBased())); deviceService.saveDevice(device); } - + @Test(expected = DataValidationException.class) public void testAssignDeviceToNonExistentCustomer() { Device device = new Device(); @@ -149,7 +214,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { tenantService.deleteTenant(tenant.getId()); } } - + @Test public void testFindDeviceById() { Device device = new Device(); @@ -198,7 +263,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { devices.forEach((device) -> { deviceService.deleteDevice(tenantId, device.getId()); }); } } - + @Test public void testDeleteDevice() { Device device = new Device(); @@ -214,15 +279,15 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { DeviceCredentials foundDeviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, savedDevice.getId()); Assert.assertNull(foundDeviceCredentials); } - + @Test public void testFindDevicesByTenantId() { Tenant tenant = new Tenant(); tenant.setTitle("Test tenant"); tenant = tenantService.saveTenant(tenant); - + TenantId tenantId = tenant.getId(); - + List devices = new ArrayList<>(); for (int i=0;i<178;i++) { Device device = new Device(); @@ -231,7 +296,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { device.setType("default"); devices.add(deviceService.saveDevice(device)); } - + List loadedDevices = new ArrayList<>(); PageLink pageLink = new PageLink(23); PageData pageData = null; @@ -242,19 +307,19 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(devices, idComparator); Collections.sort(loadedDevices, idComparator); - + Assert.assertEquals(devices, loadedDevices); - + deviceService.deleteDevicesByTenantId(tenantId); pageLink = new PageLink(33); pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); - + tenantService.deleteTenant(tenantId); } @@ -284,7 +349,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { device.setType("default"); devicesTitle2.add(new DeviceInfo(deviceService.saveDevice(device), null, false, "default")); } - + List loadedDevicesTitle1 = new ArrayList<>(); PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; @@ -295,12 +360,12 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(devicesTitle1, idComparator); Collections.sort(loadedDevicesTitle1, idComparator); - + Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); - + List loadedDevicesTitle2 = new ArrayList<>(); pageLink = new PageLink(4, 0, title2); do { @@ -313,22 +378,22 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Collections.sort(devicesTitle2, idComparator); Collections.sort(loadedDevicesTitle2, idComparator); - + Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); for (Device device : loadedDevicesTitle1) { deviceService.deleteDevice(tenantId, device.getId()); } - + pageLink = new PageLink(4, 0, title1); pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - + for (Device device : loadedDevicesTitle2) { deviceService.deleteDevice(tenantId, device.getId()); } - + pageLink = new PageLink(4, 0, title2); pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); Assert.assertFalse(pageData.hasNext()); @@ -413,21 +478,21 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); } - + @Test public void testFindDevicesByTenantIdAndCustomerId() { Tenant tenant = new Tenant(); tenant.setTitle("Test tenant"); tenant = tenantService.saveTenant(tenant); - + TenantId tenantId = tenant.getId(); - + Customer customer = new Customer(); customer.setTitle("Test customer"); customer.setTenantId(tenantId); customer = customerService.saveCustomer(customer); CustomerId customerId = customer.getId(); - + List devices = new ArrayList<>(); for (int i=0;i<278;i++) { Device device = new Device(); @@ -437,7 +502,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { device = deviceService.saveDevice(device); devices.add(new DeviceInfo(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId), customer.getTitle(), customer.isPublic(), "default")); } - + List loadedDevices = new ArrayList<>(); PageLink pageLink = new PageLink(23); PageData pageData = null; @@ -448,31 +513,31 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(devices, idComparator); Collections.sort(loadedDevices, idComparator); - + Assert.assertEquals(devices, loadedDevices); - + deviceService.unassignCustomerDevices(tenantId, customerId); pageLink = new PageLink(33); pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); - + tenantService.deleteTenant(tenantId); } - + @Test public void testFindDevicesByTenantIdCustomerIdAndName() { - + Customer customer = new Customer(); customer.setTitle("Test customer"); customer.setTenantId(tenantId); customer = customerService.saveCustomer(customer); CustomerId customerId = customer.getId(); - + String title1 = "Device title 1"; List devicesTitle1 = new ArrayList<>(); for (int i=0;i<175;i++) { @@ -499,7 +564,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { device = deviceService.saveDevice(device); devicesTitle2.add(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId)); } - + List loadedDevicesTitle1 = new ArrayList<>(); PageLink pageLink = new PageLink(15, 0, title1); PageData pageData = null; @@ -510,12 +575,12 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { pageLink = pageLink.nextPageLink(); } } while (pageData.hasNext()); - + Collections.sort(devicesTitle1, idComparator); Collections.sort(loadedDevicesTitle1, idComparator); - + Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); - + List loadedDevicesTitle2 = new ArrayList<>(); pageLink = new PageLink(4, 0, title2); do { @@ -528,22 +593,22 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Collections.sort(devicesTitle2, idComparator); Collections.sort(loadedDevicesTitle2, idComparator); - + Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); for (Device device : loadedDevicesTitle1) { deviceService.deleteDevice(tenantId, device.getId()); } - + pageLink = new PageLink(4, 0, title1); pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); Assert.assertEquals(0, pageData.getData().size()); - + for (Device device : loadedDevicesTitle2) { deviceService.deleteDevice(tenantId, device.getId()); } - + pageLink = new PageLink(4, 0, title2); pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); Assert.assertFalse(pageData.hasNext()); From 51b0d50542ff0da65c7abd722e3c0f6332c8a5ee Mon Sep 17 00:00:00 2001 From: nickAS21 <44275303+nickAS21@users.noreply.github.com> Date: Tue, 20 Apr 2021 10:44:11 +0300 Subject: [PATCH 038/127] Lwm2m discovery (#4438) * Lwm2m: fix bug delete zero updateAttribute * Lwm2m: front add select binding * Lwm2m: discovery only for test * Lwm2m: remove type_cast_enabled from the main branch. * Lwm2m: remove type_cast_enabled from the main branch. * Lwm2m: remove type_cast_enabled from the main branch. * Lwm2m: remove double code. --- .../secure/LwM2MBootstrapSecurityStore.java | 5 +- .../lwm2m/server/LwM2mServerListener.java | 6 +- .../server/LwM2mTransportContextServer.java | 134 +++++++-- .../lwm2m/server/LwM2mTransportHandler.java | 16 +- .../lwm2m/server/LwM2mTransportRequest.java | 12 +- .../server/LwM2mTransportServiceImpl.java | 276 ++++++++++-------- .../lwm2m/server/client/LwM2mClient.java | 8 +- .../server/client/LwM2mClientContextImpl.java | 4 +- .../client/ResultsAddKeyValueProto.java | 34 +++ .../server/client/ResultsResourceValue.java | 32 ++ .../lwm2m/utils/LwM2mValueConverterImpl.java | 23 +- .../src/main/resources/tb-lwm2m-transport.yml | 21 +- ...ile-transport-configuration.component.html | 17 +- ...ofile-transport-configuration.component.ts | 8 +- .../lwm2m/lwm2m-profile-config.models.ts | 32 +- 15 files changed, 420 insertions(+), 208 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAddKeyValueProto.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 52638a99e2..358ba499d4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -35,7 +35,6 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInf import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; import org.thingsboard.server.transport.lwm2m.utils.TypeServer; import java.io.IOException; @@ -165,13 +164,13 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint()); - context.sendParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); + context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo); return lwM2MBootstrapConfig; } else { log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); - context.sendParametersOnThingsboard(context.getTelemetryMsgObject(logMsg), LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC, sessionInfo); + context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo); return null; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index ffe2f76602..9b2f3cb7eb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -26,7 +26,7 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate; import java.util.Collection; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; @Slf4j public class LwM2mServerListener { @@ -92,7 +92,7 @@ public class LwM2mServerListener { public void onResponse(Observation observation, Registration registration, ObserveResponse response) { if (registration != null) { try { - service.onObservationResponse(registration, convertToIdVerFromObjectId(observation.getPath().toString(), registration), response); + service.onObservationResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), response); } catch (Exception e) { log.error("[{}] onResponse", e.toString()); @@ -107,7 +107,7 @@ public class LwM2mServerListener { @Override public void newObservation(Observation observation, Registration registration) { -// log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint()); + log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint()); } }; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java index 170530f084..ed0a1d16e6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java @@ -30,31 +30,33 @@ package org.thingsboard.server.transport.lwm2m.server; * limitations under the License. */ -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.DDFFileParser; import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.eclipse.leshan.core.model.InvalidDDFFileException; import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.codec.CodecException; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_TELEMETRY; @Slf4j @@ -91,7 +93,7 @@ public class LwM2mTransportContextServer extends TransportContext { /** * send to Thingsboard Attribute || Telemetry * - * @param msg - JsonObject: [{name: value}] + * @param msg - JsonObject: [{name: value}] * @return - dummy */ private TransportServiceCallback getPubAckCallbackSendAttrTelemetry(final T msg) { @@ -108,33 +110,29 @@ public class LwM2mTransportContextServer extends TransportContext { }; } - public void sendParametersOnThingsboard(JsonElement msg, String topicName, SessionInfoProto sessionInfo) { - try { - if (topicName.equals(LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC)) { - PostAttributeMsg postAttributeMsg = adaptor.convertToPostAttributes(msg); - TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postAttributeMsg); - transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call)); - } else if (topicName.equals(LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC)) { - PostTelemetryMsg postTelemetryMsg = adaptor.convertToPostTelemetry(msg); - TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postTelemetryMsg); - transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call)); - } - } catch (AdaptorException e) { - log.error("[{}] Failed to process publish msg [{}]", topicName, e); - log.info("[{}] Closing current session due to invalid publish", topicName); - } + public void sendParametersOnThingsboardAttribute(List result, SessionInfoProto sessionInfo) { + PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder(); + request.addAllKv(result); + PostAttributeMsg postAttributeMsg = request.build(); + TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postAttributeMsg); + transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call)); } - public JsonObject getTelemetryMsgObject(String logMsg) { - JsonObject telemetries = new JsonObject(); - telemetries.addProperty(LOG_LW2M_TELEMETRY, logMsg); - return telemetries; + public void sendParametersOnThingsboardTelemetry(List result, SessionInfoProto sessionInfo) { + PostTelemetryMsg.Builder request = PostTelemetryMsg.newBuilder(); + TransportProtos.TsKvListProto.Builder builder = TransportProtos.TsKvListProto.newBuilder(); + builder.setTs(System.currentTimeMillis()); + builder.addAllKv(result); + request.addTsKvList(builder.build()); + PostTelemetryMsg postTelemetryMsg = request.build(); + TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postTelemetryMsg); + transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call)); } /** * @return - sessionInfo after access connect client */ - public SessionInfoProto getValidateSessionInfo(ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) { + public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) { return SessionInfoProto.newBuilder() .setNodeId(this.getNodeId()) .setSessionIdMSB(mostSignificantBits) @@ -159,4 +157,90 @@ public class LwM2mTransportContextServer extends TransportContext { return null; } } + + /** + * + * @param logMsg - info about Logs + * @return- KeyValueProto for telemetry (Logs) + */ + public List getKvLogyToThingsboard(String logMsg) { + List result = new ArrayList<>(); + result.add(TransportProtos.KeyValueProto.newBuilder() + .setKey(LOG_LW2M_TELEMETRY) + .setType(TransportProtos.KeyValueType.STRING_V) + .setStringV(logMsg).build()); + return result; + } + + /** + * @return - KeyValueProto for attribute/telemetry (change value) + * @throws CodecException - + */ + + public TransportProtos.KeyValueProto getKvAttrTelemetryToThingsboard(ResourceModel.Type resourceType, String resourceName, Object value, boolean isMultiInstances) { + TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(resourceName); + if (isMultiInstances) { + kvProto.setType(TransportProtos.KeyValueType.JSON_V) + .setJsonV((String) value); + } + else { + switch (resourceType) { + case BOOLEAN: + kvProto.setType(BOOLEAN_V).setBoolV((Boolean) value).build(); + break; + case STRING: + case TIME: + case OPAQUE: + case OBJLNK: + kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV((String) value); + break; + case INTEGER: + kvProto.setType(TransportProtos.KeyValueType.LONG_V).setLongV((Long) value); + break; + case FLOAT: + kvProto.setType(TransportProtos.KeyValueType.DOUBLE_V).setDoubleV((Double) value); + } + } + return kvProto.build(); + } + + /** + * + * @param currentType - + * @param resourcePath - + * @return + */ + public ResourceModel.Type getResourceModelTypeEqualsKvProtoValueType(ResourceModel.Type currentType, String resourcePath) { + switch (currentType) { + case BOOLEAN: + return ResourceModel.Type.BOOLEAN; + case STRING: + case TIME: + case OPAQUE: + case OBJLNK: + return ResourceModel.Type.STRING; + case INTEGER: + return ResourceModel.Type.INTEGER; + case FLOAT: + return ResourceModel.Type.FLOAT; + default: + } + throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType); + } + + public Object getValueFromKvProto (TransportProtos.KeyValueProto kv) { + switch (kv.getType()) { + case BOOLEAN_V: + return kv.getBoolV(); + case LONG_V: + return kv.getLongV(); + case DOUBLE_V: + return kv.getDoubleV(); + case STRING_V: + return kv.getStringV(); + case JSON_V: + return kv.getJsonV(); + } + return null; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java index ce65b072d6..86dd5b7960 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java @@ -75,6 +75,8 @@ public class LwM2mTransportHandler { public static final String KEY_NAME = "keyName"; public static final String OBSERVE = "observe"; public static final String ATTRIBUTE_LWM2M = "attributeLwm2m"; + public static final String RESOURCE_VALUE = "resValue"; + public static final String RESOURCE_TYPE = "resType"; private static final String REQUEST = "/request"; private static final String RESPONSE = "/response"; @@ -333,28 +335,30 @@ public class LwM2mTransportHandler { }; } - public static String convertToObjectIdFromIdVer(String key) { + public static String convertPathFromIdVerToObjectId(String pathIdVer) { try { - String[] keyArray = key.split(LWM2M_SEPARATOR_PATH); + String[] keyArray = pathIdVer.split(LWM2M_SEPARATOR_PATH); if (keyArray.length > 1 && keyArray[1].split(LWM2M_SEPARATOR_KEY).length == 2) { keyArray[1] = keyArray[1].split(LWM2M_SEPARATOR_KEY)[0]; return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); - } else { - return key; + } + else { + return pathIdVer; } } catch (Exception e) { return null; } } - public static String convertToIdVerFromObjectId(String path, Registration registration) { + public static String convertPathFromObjectIdToIdVer(String path, Registration registration) { String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId()); try { String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); if (keyArray.length > 1) { keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver; return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); - } else { + } + else { return path; } } catch (Exception e) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index 2c728260d0..d717c90418 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -69,8 +69,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.createWriteAttributeRequest; @Slf4j @@ -114,7 +114,7 @@ public class LwM2mTransportRequest { */ public void sendAllRequest(Registration registration, String targetIdVer, String typeOper, String contentFormatParam, Observation observation, Object params, long timeoutInMs) { - String target = convertToObjectIdFromIdVer(targetIdVer); + String target = convertPathFromIdVerToObjectId(targetIdVer); LwM2mPath resultIds = new LwM2mPath(target); if (registration != null && resultIds.getObjectId() >= 0) { DownlinkRequest request = null; @@ -204,7 +204,7 @@ public class LwM2mTransportRequest { private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs) { leshanServer.send(registration, request, timeoutInMs, (ResponseCallback) response -> { if (!lwM2MClient.isInit()) { - lwM2MClient.initValue(this.serviceImpl, convertToIdVerFromObjectId(request.getPath().toString(), registration)); + lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { this.handleResponse(registration, request.getPath().toString(), response, request); @@ -228,7 +228,7 @@ public class LwM2mTransportRequest { } }, e -> { if (!lwM2MClient.isInit()) { - lwM2MClient.initValue(this.serviceImpl, convertToIdVerFromObjectId(request.getPath().toString(), registration)); + lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } String msg = String.format("%s: sendRequest: Resource path - %s msg error - %s SendRequest to Client", LOG_LW2M_ERROR, request.getPath().toString(), e.toString()); @@ -287,7 +287,7 @@ public class LwM2mTransportRequest { * @param response - */ private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request) { - String pathIdVer = convertToIdVerFromObjectId(path, registration); + String pathIdVer = convertPathFromObjectIdToIdVer(path, registration); if (response instanceof ReadResponse) { serviceImpl.onObservationResponse(registration, pathIdVer, (ReadResponse) response); } else if (response instanceof CancelObservationResponse) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java index d863504b44..05f386ad1a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server; import com.fasterxml.jackson.core.type.TypeReference; import com.google.common.collect.Sets; import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -42,7 +43,6 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.adaptor.AdaptorException; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -52,12 +52,12 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; +import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.LinkedHashSet; @@ -76,24 +76,19 @@ import java.util.stream.Collectors; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.common.transport.util.JsonUtils.getJsonObject; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_TOPIC; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_TELEMETRY_TOPIC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey; @@ -163,6 +158,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); + transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null); this.initLwM2mFromClientValue(registration, lwM2MClient); this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration); } else { @@ -309,33 +305,32 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { if (msg.getSharedUpdatedCount() > 0) { - JsonElement el = JsonConverter.toJson(msg); - el.getAsJsonObject().entrySet().forEach(de -> { - String pathIdVer = this.getPathAttributeUpdate(sessionInfo, de.getKey()); - String value = de.getValue().getAsString(); + msg.getSharedUpdatedList().forEach(tsKvProto -> { + String pathName = tsKvProto.getKv().getKey(); + String pathIdVer = this.validatePathIntoProfile(sessionInfo, pathName); + Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); - LwM2mClientProfile clientProfile = lwM2mClientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - if (pathIdVer != null && !pathIdVer.isEmpty() && (this.validatePathInAttrProfile(clientProfile, pathIdVer) || this.validatePathInTelemetryProfile(clientProfile, pathIdVer))) { + if (pathIdVer != null) { ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer); if (resourceModel != null && resourceModel.operations.isWritable()) { - lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), pathIdVer, POST_TYPE_OPER_WRITE_REPLACE, - ContentFormat.TLV.getName(), null, value, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()); + this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer); } else { - log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, value); + log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, valueNew); String logMsg = String.format("%s: attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", - LOG_LW2M_ERROR, pathIdVer, value); + LOG_LW2M_ERROR, pathIdVer, valueNew); this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); } } else { - log.error("Attribute name - [{}] value - [{}] is not present as attribute in profile and cannot be updated", de.getKey(), value); + log.error("Resource name name - [{}] value - [{}] is not present as attribute/telemetry in profile and cannot be updated", pathName, valueNew); String logMsg = String.format("%s: attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", - LOG_LW2M_ERROR, de.getKey(), value); + LOG_LW2M_ERROR, pathName, valueNew); this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); } }); } else if (msg.getSharedDeletedCount() > 0) { log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); } + } /** @@ -462,32 +457,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } /** - * @param msg - text msg + * @param logMsg - text msg * @param registration - Id of Registration LwM2M Client */ - public void sendLogsToThingsboard(String msg, Registration registration) { - if (msg != null) { - JsonObject telemetries = new JsonObject(); - telemetries.addProperty(LOG_LW2M_TELEMETRY, msg); - this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration); - } - } - - - /** - * // !!! Ok - * Prepare send to Thigsboard callback - Attribute or Telemetry - * - * @param msg - JsonArray: [{name: value}] - * @param topicName - Api Attribute or Telemetry - * @param registration - Id of Registration LwM2M Client - */ - public void updateParametersOnThingsboard(JsonElement msg, String topicName, Registration registration) { + public void sendLogsToThingsboard(String logMsg, Registration registration) { SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); - if (sessionInfo != null) { - lwM2mTransportContextServer.sendParametersOnThingsboard(msg, topicName, sessionInfo); - } else { - log.error("Client: [{}] updateParametersOnThingsboard [{}] sessionInfo ", registration, null); + if (logMsg != null && sessionInfo != null) { + this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(this.lwM2mTransportContextServer.getKvLogyToThingsboard(logMsg), sessionInfo); } } @@ -517,7 +493,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_READ, clientObjects); this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE, clientObjects); this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, PUT_TYPE_OPER_WRITE_ATTRIBUTES, clientObjects); -// this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_DISCOVER, clientObjects); + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_DISCOVER, clientObjects); } } @@ -577,17 +553,20 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param registration - Registration LwM2M Client */ private void updateAttrTelemetry(Registration registration, Set paths) { - JsonObject attributes = new JsonObject(); - JsonObject telemetries = new JsonObject(); try { - this.getParametersFromProfile(attributes, telemetries, registration, paths); + ResultsAddKeyValueProto results = getParametersFromProfile(registration, paths); + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + if (results != null && sessionInfo != null) { + if (results.getResultAttributes().size() > 0) { + this.lwM2mTransportContextServer.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo); + } + if (results.getResultTelemetries().size() > 0) { + this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo); + } + } } catch (Exception e) { log.error("UpdateAttrTelemetry", e); } - if (attributes.getAsJsonObject().entrySet().size() > 0) - this.updateParametersOnThingsboard(attributes, DEVICE_ATTRIBUTES_TOPIC, registration); - if (telemetries.getAsJsonObject().entrySet().size() > 0) - this.updateParametersOnThingsboard(telemetries, DEVICE_TELEMETRY_TOPIC, registration); } /** @@ -700,73 +679,99 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } /** - * @param registration - - * @return all instances in client - */ - private Set getAllInstancesInClient(Registration registration) { - Set clientInstances = ConcurrentHashMap.newKeySet(); - Arrays.stream(registration.getObjectLinks()).forEach(url -> { - LwM2mPath pathIds = new LwM2mPath(url.getUrl()); - if (pathIds.isObjectInstance()) { - clientInstances.add(convertToIdVerFromObjectId(url.getUrl(), registration)); - } - }); - return (clientInstances.size() > 0) ? clientInstances : null; - } - - /** - * @param attributes - new JsonObject - * @param telemetry - new JsonObject + * // * @param attributes - new JsonObject + * // * @param telemetry - new JsonObject + * * @param registration - Registration LwM2M Client * @param path - */ - private void getParametersFromProfile(JsonObject attributes, JsonObject telemetry, Registration registration, Set path) { + private ResultsAddKeyValueProto getParametersFromProfile(Registration registration, Set path) { if (path != null && path.size() > 0) { + ResultsAddKeyValueProto results = new ResultsAddKeyValueProto(); LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); - lwM2MClientProfile.getPostAttributeProfile().forEach(idVer -> { - if (path.contains(idVer.getAsString())) { - this.addParameters(idVer.getAsString(), attributes, registration); + List resultAttributes = new ArrayList<>(); + lwM2MClientProfile.getPostAttributeProfile().forEach(pathIdVer -> { + if (path.contains(pathIdVer.getAsString())) { + TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer.getAsString(), registration); + if (kvAttr != null) { + resultAttributes.add(kvAttr); + } } }); - lwM2MClientProfile.getPostTelemetryProfile().forEach(idVer -> { - if (path.contains(idVer.getAsString())) { - this.addParameters(idVer.getAsString(), telemetry, registration); + List resultTelemetries = new ArrayList<>(); + lwM2MClientProfile.getPostTelemetryProfile().forEach(pathIdVer -> { + if (path.contains(pathIdVer.getAsString())) { + TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer.getAsString(), registration); + if (kvAttr != null) { + resultTelemetries.add(kvAttr); + } } }); + if (resultAttributes.size() > 0) { + results.setResultAttributes(resultAttributes); + } + if (resultTelemetries.size() > 0) { + results.setResultTelemetries(resultTelemetries); + } + return results; } + return null; } - /** - * @param parameters - JsonObject attributes/telemetry - * @param registration - Registration LwM2M Client - */ - private void addParameters(String path, JsonObject parameters, Registration registration) { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); +// public TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) { +// ResultsResourceValue resultsResourceValue = getResultsResourceValue(pathIdVer, registration); +// return resultsResourceValue != null ? this.lwM2mTransportContextServer.getKvAttrTelemetryToThingsboard(resultsResourceValue) : null; +// } + + private TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) { + LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2mClientWithReg(null, registration.getId()); JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); - if (names != null && names.has(path)) { - String resName = names.get(path).getAsString(); - if (resName != null && !resName.isEmpty()) { + if (names != null && names.has(pathIdVer)) { + String resourceName = names.get(pathIdVer).getAsString(); + if (resourceName != null && !resourceName.isEmpty()) { try { - String resValue = this.getResourceValueToString(lwM2MClient, path); - parameters.addProperty(resName, resValue); + LwM2mResource resourceValue = lwM2MClient != null ? getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) : null; + if (resourceValue != null) { + ResourceModel.Type currentType = resourceValue.getType(); + ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); + Object valueKvProto = null; + if (resourceValue.isMultiInstances()) { + valueKvProto = new JsonObject(); + Object finalvalueKvProto = valueKvProto; + Gson gson = new GsonBuilder().create(); + resourceValue.getValues().forEach((k, v) -> { + Object val = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, + new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); + JsonElement element = gson.toJsonTree(val, val.getClass()); + ((JsonObject) finalvalueKvProto).add(String.valueOf(k), element); + }); + valueKvProto = gson.toJson(valueKvProto); + } else { + valueKvProto = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, + new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); + } + return valueKvProto != null ? this.lwM2mTransportContextServer.getKvAttrTelemetryToThingsboard(currentType, resourceName, valueKvProto, resourceValue.isMultiInstances()) : null; + } } catch (Exception e) { log.error("Failed to add parameters.", e); } } } else { - log.error("Failed to add parameters. path: [{}], names: [{}]", path, names); + log.error("Failed to add parameters. path: [{}], names: [{}]", pathIdVer, names); } + return null; } /** - * @param path - path resource - * @return - value of Resource or null + * @param pathIdVer - path resource + * @return - value of Resource into format KvProto or null */ - private String getResourceValueToString(LwM2mClient lwM2MClient, String path) { - LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(path)); - LwM2mResource resourceValue = this.returnResourceValueFromLwM2MClient(lwM2MClient, path); - return resourceValue == null ? null : - this.converter.convertValue(resourceValue.isMultiInstances() ? resourceValue.getValues() : resourceValue.getValue(), resourceValue.getType(), ResourceModel.Type.STRING, pathIds).toString(); + private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { + LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); + ResourceModel.Type currentType = resourceValue.getType(); + ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); + return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, + new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); } /** @@ -774,9 +779,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param path - * @return - return value of Resource by idPath */ - private LwM2mResource returnResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) { + private LwM2mResource getResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) { LwM2mResource resourceValue = null; - if (new LwM2mPath(convertToObjectIdFromIdVer(path)).isResource()) { + if (new LwM2mPath(convertPathFromIdVerToObjectId(path)).isResource()) { resourceValue = lwM2MClient.getResources().get(path).getLwM2mResource(); } return resourceValue; @@ -959,7 +964,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { */ private void readResourceValueObserve(Registration registration, Set targets, String typeOper) { targets.forEach(target -> { - LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(target)); + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(target)); if (pathIds.isResource()) { if (GET_TYPE_OPER_READ.equals(typeOper)) { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, @@ -1050,19 +1055,23 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { private void cancelObserveIsValue(Registration registration, Set paramAnallyzer) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); paramAnallyzer.forEach(p -> { - if (this.returnResourceValueFromLwM2MClient(lwM2MClient, p) != null) { - this.setCancelObservationRecourse(registration, convertToObjectIdFromIdVer(p)); + if (this.getResourceValueFromLwM2MClient(lwM2MClient, p) != null) { + this.setCancelObservationRecourse(registration, convertPathFromIdVerToObjectId(p)); } } ); } - private void putDelayedUpdateResourcesClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) { + private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) { if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, ContentFormat.TLV.getName(), null, valueNew, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()); } else { - log.error("05 delayError"); + log.error("Failed update resource [{}] [{}]", path, valueNew); + String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", + LOG_LW2M_ERROR, path, valueNew); + this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + log.info("Failed update resource [{}] [{}]", path, valueNew); } } @@ -1082,9 +1091,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param name - * @return path if path isPresent in postProfile */ - private String getPathAttributeUpdate(TransportProtos.SessionInfoProto sessionInfo, String name) { - String profilePath = this.getPathAttributeUpdateProfile(sessionInfo, name); - return !profilePath.isEmpty() ? profilePath : null; + private String validatePathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { + String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, name); + return !pathIdVer.isEmpty() ? pathIdVer : null; } /** @@ -1094,7 +1103,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param name - * @return - */ - private String getPathAttributeUpdateProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { + private String getPresentPathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { LwM2mClientProfile profile = lwM2mClientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); LwM2mClient lwM2mClient = lwM2mClientContext.getLwM2MClient(sessionInfo); return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() @@ -1105,40 +1114,49 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { /** * Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values * #1 Get path resource by result attributesResponse - * #1.1 If two names have equal path => last time attribute - * #2.1 if there is a difference in values between the current resource values and the shared attribute values - * => send to client Request Update of value (new value from shared attribute) - * and LwM2MClient.delayedRequests.add(path) - * #2.1 if there is not a difference in values between the current resource values and the shared attribute values * * @param attributesResponse - * @param sessionInfo - */ public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { try { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2MClient(sessionInfo); - attributesResponse.getSharedAttributeListList().forEach(attr -> { - String path = this.getPathAttributeUpdate(sessionInfo, attr.getKv().getKey()); - if (path != null) { - // #1.1 - if (lwM2MClient.getDelayedRequests().containsKey(path) && attr.getTs() > lwM2MClient.getDelayedRequests().get(path).getTs()) { - lwM2MClient.getDelayedRequests().put(path, attr); - } else { - lwM2MClient.getDelayedRequests().put(path, attr); - } - } - }); - // #2.1 - lwM2MClient.getDelayedRequests().forEach((k, v) -> { - ArrayList listV = new ArrayList<>(); - listV.add(v.getKv()); - this.putDelayedUpdateResourcesClient(lwM2MClient, this.getResourceValueToString(lwM2MClient, k), getJsonObject(listV).get(v.getKv().getKey()), k); - }); + List tsKvProtos = attributesResponse.getSharedAttributeListList(); + this.updateAttriuteFromThingsboard(tsKvProtos, sessionInfo); } catch (Exception e) { log.error(String.valueOf(e)); } } + /** + * #1.1 If two names have equal path => last time attribute + * #2.1 if there is a difference in values between the current resource values and the shared attribute values + * => send to client Request Update of value (new value from shared attribute) + * and LwM2MClient.delayedRequests.add(path) + * #2.1 if there is not a difference in values between the current resource values and the shared attribute values + * + * @param tsKvProtos + * @param sessionInfo + */ + public void updateAttriuteFromThingsboard(List tsKvProtos, TransportProtos.SessionInfoProto sessionInfo) { + LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2MClient(sessionInfo); + tsKvProtos.forEach(tsKvProto -> { + String pathIdVer = this.validatePathIntoProfile(sessionInfo, tsKvProto.getKv().getKey()); + if (pathIdVer != null) { + // #1.1 + if (lwM2MClient.getDelayedRequests().containsKey(pathIdVer) && tsKvProto.getTs() > lwM2MClient.getDelayedRequests().get(pathIdVer).getTs()) { + lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto); + } else if (!lwM2MClient.getDelayedRequests().containsKey(pathIdVer)) { + lwM2MClient.getDelayedRequests().put(pathIdVer, tsKvProto); + } + } + }); + // #2.1 + lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> { + this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), + this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()), pathIdVer); + }); + } + /** * @param lwM2MClient - * @return SessionInfoProto - @@ -1257,7 +1275,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer); - Integer objectId = new LwM2mPath(convertToObjectIdFromIdVer(pathIdVer)).getObjectId(); + Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId(); String objectVer = validateObjectVerFromKey(pathIdVer); return resourceModel != null && (isWritableNotOptional ? objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() : diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 56e8fd5c7b..5e736d8e0f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -39,7 +39,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToObjectIdFromIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; @Slf4j @Data @@ -83,7 +83,7 @@ public class LwM2mClient implements Cloneable { this.resources.get(pathRez).setLwM2mResource(rez); return true; } else { - LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez)); + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); if (resourceModel != null) { this.resources.put(pathRez, new ResourceValue(rez, resourceModel)); @@ -110,7 +110,7 @@ public class LwM2mClient implements Cloneable { public void deleteResources(String pathIdVer, LwM2mModelProvider modelProvider) { Set key = getKeysEqualsIdVer(pathIdVer); key.forEach(pathRez -> { - LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez)); + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); if (resourceModel != null) { this.resources.get(pathRez).setResourceModel(resourceModel); @@ -132,7 +132,7 @@ public class LwM2mClient implements Cloneable { } private void saveResourceModel(String pathRez, LwM2mModelProvider modelProvider) { - LwM2mPath pathIds = new LwM2mPath(convertToObjectIdFromIdVer(pathRez)); + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); this.resources.get(pathRez).setResourceModel(resourceModel); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 1ce16b9b92..cca729f5ff 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -35,7 +35,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertToIdVerFromObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; @Service @TbLwM2mTransportComponent @@ -185,7 +185,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { Arrays.stream(registration.getObjectLinks()).forEach(url -> { LwM2mPath pathIds = new LwM2mPath(url.getUrl()); if (!pathIds.isRoot()) { - clientObjects.add(convertToIdVerFromObjectId(url.getUrl(), registration)); + clientObjects.add(convertPathFromObjectIdToIdVer(url.getUrl(), registration)); } }); return (clientObjects.size() > 0) ? clientObjects : null; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAddKeyValueProto.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAddKeyValueProto.java new file mode 100644 index 0000000000..5c7edbe328 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAddKeyValueProto.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server.client; + +import lombok.Data; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class ResultsAddKeyValueProto { + List resultAttributes; + List resultTelemetries; + + public ResultsAddKeyValueProto() { + this.resultAttributes = new ArrayList<>(); + this.resultTelemetries = new ArrayList<>(); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java new file mode 100644 index 0000000000..36cd8943fb --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server.client; + +import lombok.Data; +import org.thingsboard.server.common.data.kv.DataType; + +@Data +public class ResultsResourceValue { + DataType dataType; + Object value; + String resourceName; + + public ResultsResourceValue (DataType dataType, Object value, String resourceName) { + this.dataType = dataType; + this.value = value; + this.resourceName = resourceName; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index 0faa20952e..dce34c9cf6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -18,14 +18,12 @@ package org.thingsboard.server.transport.lwm2m.utils; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel.Type; import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.node.codec.LwM2mValueConverter; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.StringUtils; -import javax.xml.datatype.DatatypeConfigurationException; -import javax.xml.datatype.DatatypeFactory; -import javax.xml.datatype.XMLGregorianCalendar; import java.math.BigInteger; import java.text.DateFormat; import java.text.SimpleDateFormat; @@ -111,15 +109,16 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { case INTEGER: log.debug("Trying to convert long value {} to date", value); /** let's assume we received the millisecond since 1970/1/1 */ - return new Date((Long) value); + return new Date(((Number) value).longValue() * 1000L); case STRING: log.debug("Trying to convert string value {} to date", value); /** let's assume we received an ISO 8601 format date */ try { - DatatypeFactory datatypeFactory = DatatypeFactory.newInstance(); - XMLGregorianCalendar cal = datatypeFactory.newXMLGregorianCalendar((String) value); - return cal.toGregorianCalendar().getTime(); - } catch (DatatypeConfigurationException | IllegalArgumentException e) { + return new Date(Long.decode(value.toString())); +// DatatypeFactory datatypeFactory = DatatypeFactory.newInstance(); +// XMLGregorianCalendar cal = datatypeFactory.newXMLGregorianCalendar((String) value); +// return cal.toGregorianCalendar().getTime(); + } catch (IllegalArgumentException e) { log.debug("Unable to convert string to date", e); throw new CodecException("Unable to convert string (%s) to date for resource %s", value, resourcePath); @@ -147,6 +146,8 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { return formatter.format(new Date(timeValue)); case OPAQUE: return Hex.encodeHexString((byte[])value); + case OBJLNK: + return ObjectLink.decodeFromString((String) value); default: break; } @@ -164,10 +165,14 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { } } break; + case OBJLNK: + if (currentType == Type.STRING) { + return ObjectLink.fromPath(value.toString()); + } default: } throw new CodecException("Invalid value type for resource %s, expected %s, got %s", resourcePath, expectedType, currentType); } -} + } diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index ec63dcae75..922aed5450 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -119,6 +119,18 @@ redis: # LWM2M server parameters transport: + sessions: + inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" + report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" + json: + # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON + type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:false}" + # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) + max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" + client_side_rpc: + timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}" + # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) + api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" # Local LwM2M transport parameters lwm2m: # Enable/disable lvm2m transport protocol. @@ -180,15 +192,6 @@ transport: # Use redis for Security and Registration stores redis.enabled: "${LWM2M_REDIS_ENABLED:false}" - sessions: - inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" - report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" - json: - # Cast String data types to Numeric if possible when processing Telemetry/Attributes JSON - type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" - # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) - max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" - queue: type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) kafka: diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html index 84797bcb98..5363d05d74 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.html @@ -82,8 +82,6 @@ {{ 'device-profile.lwm2m.required' | translate }} - -
{{ 'device-profile.lwm2m.default-min-period' | translate }} @@ -93,13 +91,16 @@ {{ 'device-profile.lwm2m.required' | translate }} - +
+
+ {{ 'device-profile.lwm2m.binding' | translate }} - - - {{ 'device-profile.lwm2m.binding' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - + + + {{ bindingModeTypeNamesMap.get(bindingModeType[bindingMode]) }} + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index a9805c528b..d6f6823db6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -22,7 +22,8 @@ import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ATTRIBUTE, - DEFAULT_BINDING, + BINDING_MODE, + BINDING_MODE_NAMES, getDefaultProfileConfig, INSTANCES, KEY_NAME, @@ -55,6 +56,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private requiredValue: boolean; private disabled = false; + bindingModeType = BINDING_MODE; + bindingModeTypes = Object.keys(BINDING_MODE); + bindingModeTypeNamesMap = BINDING_MODE_NAMES; lwm2mDeviceProfileFormGroup: FormGroup; lwm2mDeviceConfigFormGroup: FormGroup; bootstrapServers: string; @@ -86,7 +90,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro lifetime: [null, Validators.required], defaultMinPeriod: [null, Validators.required], notifIfDisabled: [true, []], - binding: [DEFAULT_BINDING, Validators.required], + binding:[], bootstrapServer: [null, Validators.required], lwm2mServer: [null, Validators.required], }); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index 678f7e260a..ff927bc09f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -44,6 +44,35 @@ export const KEY_REGEXP_NUMBER = /^(\-?|\+?)\d*$/; export const INSTANCES_ID_VALUE_MIN = 0; export const INSTANCES_ID_VALUE_MAX = 65535; + +export enum BINDING_MODE { + U = 'U', + UQ = 'UQ', + T = 'T', + TQ = 'TQ', + S = 'S', + SQ = 'SQ', + US = 'US', + TS = 'TS', + UQS = 'UQS', + TQS = 'TQS' +} + +export const BINDING_MODE_NAMES = new Map( + [ + [BINDING_MODE.U, 'U: UDP connection in standard mode'], + [BINDING_MODE.UQ, 'UQ: UDP connection in queue mode'], + [BINDING_MODE.US, 'US: both UDP and SMS connections active, both in standard mode'], + [BINDING_MODE.UQS, 'UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode'], + [BINDING_MODE.T,'T: TCP connection in standard mode'], + [BINDING_MODE.TQ, 'TQ: TCP connection in queue mode'], + [BINDING_MODE.TS, 'TS: both TCP and SMS connections active, both in standard mode'], + [BINDING_MODE.TQS, 'TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode'], + [BINDING_MODE.S, 'S: SMS connection in standard mode'], + [BINDING_MODE.SQ, 'SQ: SMS connection in queue mode'] + ] +); + export enum ATTRIBUTE_LWM2M_ENUM { dim = 'dim', ver = 'ver', @@ -62,8 +91,7 @@ export const ATTRIBUTE_LWM2M_LABEL = new Map( [ATTRIBUTE_LWM2M_ENUM.pmax, 'pmax='], [ATTRIBUTE_LWM2M_ENUM.gt, '>'], [ATTRIBUTE_LWM2M_ENUM.lt, '<'], - [ATTRIBUTE_LWM2M_ENUM.st, 'st='], - + [ATTRIBUTE_LWM2M_ENUM.st, 'st='] ] ); From 321fb731a6537399a6e4572837967fe41cdaf58a Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 20 Apr 2021 11:14:27 +0300 Subject: [PATCH 039/127] Support of server-side RPC requests via protobuf for CoAP and MQTT * rpc response schema added * rpc request proto * fixed device profile validation * fix error messages --- .../AbstractTransportIntegrationTest.java | 42 +++--- .../coap/AbstractCoapIntegrationTest.java | 24 +++- ...AttributesRequestProtoIntegrationTest.java | 2 +- .../AbstractCoapProvisionJsonDeviceTest.java | 12 +- .../AbstractCoapProvisionProtoDeviceTest.java | 12 +- ...tractCoapServerSideRpcIntegrationTest.java | 6 +- ...tCoapServerSideRpcJsonIntegrationTest.java | 4 +- ...CoapServerSideRpcProtoIntegrationTest.java | 130 +++++++++++++++--- ...actCoapTimeseriesProtoIntegrationTest.java | 2 +- .../mqtt/AbstractMqttIntegrationTest.java | 26 +++- ...AttributesRequestProtoIntegrationTest.java | 2 +- .../AbstractMqttProvisionJsonDeviceTest.java | 14 +- .../AbstractMqttProvisionProtoDeviceTest.java | 14 +- ...tractMqttServerSideRpcIntegrationTest.java | 2 +- ...MqttServerSideRpcProtoIntegrationTest.java | 96 ++++++++++++- ...actMqttTimeseriesProtoIntegrationTest.java | 6 +- .../ProtoTransportPayloadConfiguration.java | 55 +++++++- .../transport/coap/CoapTransportResource.java | 42 ++++-- .../coap/adaptors/CoapTransportAdaptor.java | 5 +- .../coap/adaptors/JsonCoapAdaptor.java | 5 +- .../coap/adaptors/ProtoCoapAdaptor.java | 15 +- .../mqtt/adaptors/ProtoMqttAdaptor.java | 22 ++- .../mqtt/session/DeviceSessionCtx.java | 13 ++ .../transport/adaptor/AdaptorException.java | 4 + .../transport/adaptor/ProtoConverter.java | 38 +++-- .../dao/device/DeviceProfileServiceImpl.java | 54 +++++++- ...ile-transport-configuration.component.html | 15 ++ ...ofile-transport-configuration.component.ts | 6 +- ...ile-transport-configuration.component.html | 15 ++ ...ofile-transport-configuration.component.ts | 14 +- ui-ngx/src/app/shared/models/device.models.ts | 31 ++++- .../assets/locale/locale.constant-en_US.json | 5 + 32 files changed, 568 insertions(+), 165 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java index 89ef53b19d..74523852a9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java @@ -15,35 +15,11 @@ */ package org.thingsboard.server.transport; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.extern.slf4j.Slf4j; -import org.eclipse.paho.client.mqttv3.MqttAsyncClient; -import org.eclipse.paho.client.mqttv3.MqttConnectOptions; -import org.eclipse.paho.client.mqttv3.MqttException; -import org.eclipse.paho.client.mqttv3.MqttMessage; -import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; -import org.junit.Assert; -import org.springframework.util.StringUtils; 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.TransportPayloadType; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; -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.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.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; import org.thingsboard.server.gen.transport.TransportProtos; @@ -51,8 +27,6 @@ import java.util.ArrayList; import java.util.List; 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; @Slf4j @@ -105,6 +79,22 @@ public abstract class AbstractTransportIntegrationTest extends AbstractControlle " }\n" + "}"; + protected static final String DEVICE_RPC_RESPONSE_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "package rpc;\n" + + "\n" + + "message RpcResponseMsg {\n" + + " string payload = 1;\n" + + "}"; + + protected static final String DEVICE_RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "package rpc;\n" + + "\n" + + "message RpcRequestMsg {\n" + + " string method = 1;\n" + + " int32 requestId = 2;\n" + + " string params = 3;\n" + + "}"; + protected Tenant savedTenant; protected User tenantAdmin; diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java index ff64d70423..83023e21b9 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java @@ -53,7 +53,7 @@ import static org.junit.Assert.assertNotNull; public abstract class AbstractCoapIntegrationTest extends AbstractTransportIntegrationTest { protected void processBeforeTest(String deviceName, CoapDeviceType coapDeviceType, TransportPayloadType payloadType) throws Exception { - this.processBeforeTest(deviceName, coapDeviceType, payloadType, null, null, DeviceProfileProvisionType.DISABLED, null, null); + this.processBeforeTest(deviceName, coapDeviceType, payloadType, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); } protected void processBeforeTest(String deviceName, @@ -61,8 +61,11 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg TransportPayloadType payloadType, String telemetryProtoSchema, String attributesProtoSchema, - DeviceProfileProvisionType provisionType, - String provisionKey, String provisionSecret + String rpcResponseProtoSchema, + String rpcRequestProtoSchema, + String provisionKey, + String provisionSecret, + DeviceProfileProvisionType provisionType ) throws Exception { loginSysAdmin(); @@ -85,7 +88,7 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg device.setType("default"); if (coapDeviceType != null) { - DeviceProfile coapDeviceProfile = createCoapDeviceProfile(payloadType, coapDeviceType, attributesProtoSchema, provisionType, provisionKey, provisionSecret, telemetryProtoSchema); + DeviceProfile coapDeviceProfile = createCoapDeviceProfile(payloadType, coapDeviceType, provisionSecret, provisionType, provisionKey, attributesProtoSchema, telemetryProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema); deviceProfile = doPost("/api/deviceProfile", coapDeviceProfile, DeviceProfile.class); device.setType(deviceProfile.getName()); device.setDeviceProfileId(deviceProfile.getId()); @@ -103,8 +106,9 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg } protected DeviceProfile createCoapDeviceProfile(TransportPayloadType transportPayloadType, CoapDeviceType coapDeviceType, - String attributesProtoSchema, DeviceProfileProvisionType provisionType, - String provisionKey, String provisionSecret, String telemetryProtoSchema) { + String provisionSecret, DeviceProfileProvisionType provisionType, + String provisionKey, String attributesProtoSchema, + String telemetryProtoSchema, String rpcResponseProtoSchema, String rpcRequestProtoSchema) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(transportPayloadType.name()); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -127,8 +131,16 @@ public abstract class AbstractCoapIntegrationTest extends AbstractTransportInteg if (StringUtils.isEmpty(attributesProtoSchema)) { attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; } + if (StringUtils.isEmpty(rpcResponseProtoSchema)) { + rpcResponseProtoSchema = DEVICE_RPC_RESPONSE_PROTO_SCHEMA; + } + if (StringUtils.isEmpty(rpcRequestProtoSchema)) { + rpcRequestProtoSchema = DEVICE_RPC_REQUEST_PROTO_SCHEMA; + } protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); + protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema(rpcResponseProtoSchema); + protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema(rpcRequestProtoSchema); transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; } else { transportPayloadTypeConfiguration = new JsonTransportPayloadConfiguration(); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java index 4fbe103430..5bef44434e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/request/AbstractCoapAttributesRequestProtoIntegrationTest.java @@ -79,7 +79,7 @@ public abstract class AbstractCoapAttributesRequestProtoIntegrationTest extends @Test public void testRequestAttributesValuesFromTheServer() throws Exception { super.processBeforeTest("Test Request attribute values from the server proto", CoapDeviceType.DEFAULT, - TransportPayloadType.PROTOBUF, null, ATTRIBUTES_SCHEMA_STR, DeviceProfileProvisionType.DISABLED, null, null); + TransportPayloadType.PROTOBUF, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED); processTestRequestAttributesValuesFromTheServer(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java index b4a55ae271..caecaea8ec 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionJsonDeviceTest.java @@ -88,7 +88,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn private void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); byte[] result = createCoapClientAndPublish().getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); Assert.assertEquals("Provision data was not found!", response.get("errorMsg").getAsString()); @@ -97,7 +97,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); byte[] result = createCoapClientAndPublish().getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -113,7 +113,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; byte[] result = createCoapClientAndPublish(requestCredentials).getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -132,7 +132,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn private void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; byte[] result = createCoapClientAndPublish(requestCredentials).getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -156,7 +156,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn } private void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); byte[] result = createCoapClientAndPublish().getPayload(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -167,7 +167,7 @@ public abstract class AbstractCoapProvisionJsonDeviceTest extends AbstractCoapIn } private void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.JSON, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); byte[] result = createCoapClientAndPublish().getPayload(); 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/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java index 9f1d8f1f18..682d9c7e7b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/provision/AbstractCoapProvisionProtoDeviceTest.java @@ -92,14 +92,14 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI private void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); Assert.assertNotNull(result); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); } private void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); @@ -113,7 +113,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI } private void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayload()); @@ -131,7 +131,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI } private void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayload()); @@ -155,7 +155,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI } private void processTestProvisioningCheckPreProvisionedDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); @@ -165,7 +165,7 @@ public abstract class AbstractCoapProvisionProtoDeviceTest extends AbstractCoapI } private void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createCoapClientAndPublish().getPayload()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java index a36660267e..d5d56db63b 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -124,7 +124,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC return COAP_BASE_URL + token + "/" + FeatureType.RPC.name().toLowerCase() + "/" + requestId; } - private class TestCoapCallback implements CoapHandler { + protected class TestCoapCallback implements CoapHandler { private final CoapClient client; private final CountDownLatch latch; @@ -136,7 +136,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC private Integer observe; - private TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) { + TestCoapCallback(CoapClient client, CountDownLatch latch, boolean isOneWayRpc) { this.client = client; this.latch = latch; this.isOneWayRpc = isOneWayRpc; @@ -144,7 +144,7 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC @Override public void onLoad(CoapResponse response) { - log.warn("coap response: {}, {}", response, response.getCode()); + log.warn("coap response: {}, {}", response.getResponseText(), response.getCode()); assertNotNull(response.getPayload()); assertEquals(response.getCode(), CoAP.ResponseCode.CONTENT); observe = response.getOptions().getObserve(); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java index 5387d3c7bf..cecdb1fae0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcJsonIntegrationTest.java @@ -36,12 +36,12 @@ public abstract class AbstractCoapServerSideRpcJsonIntegrationTest extends Abstr } @Test - public void testServerMqttOneWayRpc() throws Exception { + public void testServerCoapOneWayRpc() throws Exception { processOneWayRpcTest(); } @Test - public void testServerMqttTwoWayRpc() throws Exception { + public void testServerCoapTwoWayRpc() throws Exception { processTwoWayRpcTest(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java index edbdf69848..706a560024 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcProtoIntegrationTest.java @@ -15,26 +15,62 @@ */ package org.thingsboard.server.transport.coap.rpc; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapClient; import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapObserveRelation; import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.coap.Request; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.TransportPayloadType; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.msg.session.FeatureType; +import java.util.List; 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 public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends AbstractCoapServerSideRpcIntegrationTest { + private static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "package rpc;\n" + + "\n" + + "message RpcRequestMsg {\n" + + " string method = 1;\n" + + " int32 requestId = 2;\n" + + " Params params = 3;\n" + + "\n" + + " message Params {\n" + + " string pin = 1;\n" + + " int32 value = 2;\n" + + " }\n" + + "}"; + @Before public void beforeTest() throws Exception { - processBeforeTest("RPC test device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF); + processBeforeTest("RPC test device", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED); } @After @@ -43,33 +79,91 @@ public abstract class AbstractCoapServerSideRpcProtoIntegrationTest extends Abst } @Test - public void testServerMqttOneWayRpc() throws Exception { + public void testServerCoapOneWayRpc() throws Exception { processOneWayRpcTest(); } @Test - public void testServerMqttTwoWayRpc() throws Exception { + public void testServerCoapTwoWayRpc() throws Exception { processTwoWayRpcTest(); } + protected void processTwoWayRpcTest() throws Exception { + CoapClient client = getCoapClient(FeatureType.RPC); + client.useCONs(); + + CountDownLatch latch = new CountDownLatch(1); + TestCoapCallback testCoapCallback = new TestCoapCallback(client, latch, false); + + Request request = Request.newGet().setObserve(); + request.setType(CoAP.Type.CON); + CoapObserveRelation observeRelation = client.observe(request, testCoapCallback); + + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; + String deviceId = savedDevice.getId().getId().toString(); + + String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}"; + + String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); + latch.await(3, TimeUnit.SECONDS); + + assertEquals(expected, result); + assertEquals(0, testCoapCallback.getObserve().intValue()); + observeRelation.proactiveCancel(); + assertTrue(observeRelation.isCanceled()); + } + @Override protected void processOnLoadResponse(CoapResponse response, CoapClient client, Integer observe, CountDownLatch latch) { client.setURI(getRpcResponseFeatureTokenUrl(accessToken, observe)); - TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() - .setPayload(DEVICE_RESPONSE) - .setRequestId(observe) - .build(); - client.post(new CoapHandler() { - @Override - public void onLoad(CoapResponse response) { - log.warn("Command Response Ack: {}, {}", response.getCode(), response.getResponseText()); - latch.countDown(); - } + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration(); + ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA); + DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); - @Override - public void onError() { - log.warn("Command Response Ack Error, No connect"); + byte[] requestPayload = response.getPayload(); + DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg"); + Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType(); + assertNotNull(rpcRequestMsgDescriptor); + try { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload); + List fields = rpcRequestMsgDescriptor.getFields(); + for (Descriptors.FieldDescriptor fieldDescriptor: fields) { + assertTrue(dynamicMessage.hasField(fieldDescriptor)); } - }, toDeviceRpcResponseMsg.toByteArray(), MediaTypeRegistry.APPLICATION_JSON); + ProtoFileElement rpcResponseProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA); + DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); + DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg"); + Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType(); + assertNotNull(rpcResponseMsgDescriptor); + DynamicMessage rpcResponseMsg = rpcResponseBuilder + .setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE) + .build(); + client.post(new CoapHandler() { + @Override + public void onLoad(CoapResponse response) { + log.warn("Command Response Ack: {}, {}", response.getCode(), response.getResponseText()); + latch.countDown(); + } + + @Override + public void onError() { + log.warn("Command Response Ack Error, No connect"); + } + }, rpcResponseMsg.toByteArray(), MediaTypeRegistry.APPLICATION_JSON); + } catch (InvalidProtocolBufferException e) { + log.warn("Command Response Ack Error, Invalid response received: ", e); + } + } + + private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() { + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapDeviceProfileTransportConfiguration.getCoapDeviceTypeConfiguration(); + assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; } } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java index 2ae686b211..0cc3db9cf1 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java @@ -114,7 +114,7 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac " }\n" + " }\n" + "}"; - super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Post Telemetry device proto payload", CoapDeviceType.DEFAULT, TransportPayloadType.PROTOBUF, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java index cd074b3197..0d11c9a773 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java @@ -62,7 +62,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg 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); + this.processBeforeTest(deviceName, gatewayName, payloadType, telemetryTopic, attributesTopic, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); } protected void processBeforeTest(String deviceName, @@ -72,9 +72,12 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg String attributesTopic, String telemetryProtoSchema, String attributesProtoSchema, - DeviceProfileProvisionType provisionType, - String provisionKey, String provisionSecret - ) throws Exception { + String rpcResponseProtoSchema, + String rpcRequestProtoSchema, + String provisionKey, + String provisionSecret, + DeviceProfileProvisionType provisionType + ) throws Exception { loginSysAdmin(); Tenant tenant = new Tenant(); @@ -103,7 +106,7 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg gateway.setAdditionalInfo(additionalInfo); if (payloadType != null) { - DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, provisionType, provisionKey, provisionSecret); + DeviceProfile mqttDeviceProfile = createMqttDeviceProfile(payloadType, telemetryTopic, attributesTopic, telemetryProtoSchema, attributesProtoSchema, rpcResponseProtoSchema, rpcRequestProtoSchema, provisionKey, provisionSecret, provisionType); deviceProfile = doPost("/api/deviceProfile", mqttDeviceProfile, DeviceProfile.class); device.setType(deviceProfile.getName()); device.setDeviceProfileId(deviceProfile.getId()); @@ -157,8 +160,9 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg protected DeviceProfile createMqttDeviceProfile(TransportPayloadType transportPayloadType, String telemetryTopic, String attributesTopic, String telemetryProtoSchema, String attributesProtoSchema, - DeviceProfileProvisionType provisionType, - String provisionKey, String provisionSecret) { + String rpcResponseProtoSchema, String rpcRequestProtoSchema, + String provisionKey, String provisionSecret, + DeviceProfileProvisionType provisionType) { DeviceProfile deviceProfile = new DeviceProfile(); deviceProfile.setName(transportPayloadType.name()); deviceProfile.setType(DeviceProfileType.DEFAULT); @@ -186,8 +190,16 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg if (StringUtils.isEmpty(attributesProtoSchema)) { attributesProtoSchema = DEVICE_ATTRIBUTES_PROTO_SCHEMA; } + if (StringUtils.isEmpty(rpcResponseProtoSchema)) { + rpcResponseProtoSchema = DEVICE_RPC_RESPONSE_PROTO_SCHEMA; + } + if (StringUtils.isEmpty(rpcRequestProtoSchema)) { + rpcRequestProtoSchema = DEVICE_RPC_REQUEST_PROTO_SCHEMA; + } protoTransportPayloadConfiguration.setDeviceTelemetryProtoSchema(telemetryProtoSchema); protoTransportPayloadConfiguration.setDeviceAttributesProtoSchema(attributesProtoSchema); + protoTransportPayloadConfiguration.setDeviceRpcResponseProtoSchema(rpcResponseProtoSchema); + protoTransportPayloadConfiguration.setDeviceRpcRequestProtoSchema(rpcRequestProtoSchema); transportPayloadTypeConfiguration = protoTransportPayloadConfiguration; } mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java index ff8b54c79c..6949a544de 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/request/AbstractMqttAttributesRequestProtoIntegrationTest.java @@ -83,7 +83,7 @@ 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); + TransportPayloadType.PROTOBUF, null, null, null, ATTRIBUTES_SCHEMA_STR, null, null, null, null, DeviceProfileProvisionType.DISABLED); processTestRequestAttributesValuesFromTheServer(); } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java index 106c801676..870b62b195 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionJsonDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/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, null, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); 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, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -119,7 +119,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); String requestCredentials = ",\"credentialsType\": \"ACCESS_TOKEN\",\"token\": \"test_token\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -138,7 +138,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); String requestCredentials = ",\"credentialsType\": \"X509_CERTIFICATE\",\"hash\": \"testHash\""; byte[] result = createMqttClientAndPublish(requestCredentials).getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -163,7 +163,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); 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(); @@ -188,7 +188,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } 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"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); byte[] result = createMqttClientAndPublish().getPayloadBytes(); JsonObject response = JsonUtils.parse(new String(result)).getAsJsonObject(); @@ -199,7 +199,7 @@ public abstract class AbstractMqttProvisionJsonDeviceTest extends AbstractMqttIn } protected void processTestProvisioningWithBadKeyDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES, "testProvisionKeyOrig", "testProvisionSecret"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.JSON, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); 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/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java index 53fde9412b..97ebbfe4a7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/provision/AbstractMqttProvisionProtoDeviceTest.java @@ -101,14 +101,14 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI protected void processTestProvisioningDisabledDevice() throws Exception { - super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.DISABLED, null, null); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); ProvisionDeviceResponseMsg result = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertNotNull(result); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), result.getStatus().toString()); } protected void processTestProvisioningCreateNewDeviceWithoutCredentials() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Device createdDevice = deviceService.findDeviceByTenantIdAndName(savedTenant.getTenantId(), "Test Provision device"); @@ -122,7 +122,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithAccessToken() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null,null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceTokenRequestMsg(ValidateDeviceTokenRequestMsg.newBuilder().setToken("test_token").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.ACCESS_TOKEN, requestCredentials)).getPayloadBytes()); @@ -140,7 +140,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithCert() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateDeviceX509CertRequestMsg(ValidateDeviceX509CertRequestMsg.newBuilder().setHash("testHash").build()).build(); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish(createTestsProvisionMessage(CredentialsType.X509_CERTIFICATE, requestCredentials)).getPayloadBytes()); @@ -164,7 +164,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } protected void processTestProvisioningCreateNewDeviceWithMqttBasic() throws Exception { - super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES, "testProvisionKey", "testProvisionSecret"); + super.processBeforeTest("Test Provision device3", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); CredentialsDataProto requestCredentials = CredentialsDataProto.newBuilder().setValidateBasicMqttCredRequestMsg( ValidateBasicMqttCredRequestMsg.newBuilder() .setClientId("test_clientId") @@ -195,7 +195,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } 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"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKey", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedTenant.getTenantId(), savedDevice.getId()); @@ -205,7 +205,7 @@ public abstract class AbstractMqttProvisionProtoDeviceTest extends AbstractMqttI } 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"); + super.processBeforeTest("Test Provision device", "Test Provision gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, "testProvisionKeyOrig", "testProvisionSecret", DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); ProvisionDeviceResponseMsg response = ProvisionDeviceResponseMsg.parseFrom(createMqttClientAndPublish().getPayloadBytes()); Assert.assertEquals(ProvisionResponseStatus.NOT_FOUND.name(), response.getStatus().toString()); } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index 90de461627..f24cb0c2bc 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -180,7 +180,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM return message; } - private class TestMqttCallback implements MqttCallback { + protected class TestMqttCallback implements MqttCallback { private final MqttAsyncClient client; private final CountDownLatch latch; diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java index 575dfdb052..eea800fb0e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcProtoIntegrationTest.java @@ -15,25 +15,60 @@ */ package org.thingsboard.server.transport.mqtt.rpc; +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; +import org.jetbrains.annotations.NotNull; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; 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.MqttTopics; +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; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +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 AbstractMqttServerSideRpcProtoIntegrationTest extends AbstractMqttServerSideRpcIntegrationTest { + private static final String RPC_REQUEST_PROTO_SCHEMA = "syntax =\"proto3\";\n" + + "package rpc;\n" + + "\n" + + "message RpcRequestMsg {\n" + + " string method = 1;\n" + + " int32 requestId = 2;\n" + + " Params params = 3;\n" + + "\n" + + " message Params {\n" + + " string pin = 1;\n" + + " int32 value = 2;\n" + + " }\n" + + "}"; + @Before public void beforeTest() throws Exception { - processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null); + processBeforeTest("RPC test device", "RPC test gateway", TransportPayloadType.PROTOBUF, null, null, null, null, null, RPC_REQUEST_PROTO_SCHEMA, null, null, DeviceProfileProvisionType.DISABLED); } @After @@ -83,14 +118,55 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst return builder.build(); } + protected void processTwoWayRpcTest() throws Exception { + MqttAsyncClient client = getMqttAsyncClient(accessToken); + client.subscribe(MqttTopics.DEVICE_RPC_REQUESTS_SUB_TOPIC, 1); + + CountDownLatch latch = new CountDownLatch(1); + TestMqttCallback callback = new TestMqttCallback(client, latch); + client.setCallback(callback); + + Thread.sleep(1000); + + String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"26\",\"value\": 1}}"; + String deviceId = savedDevice.getId().getId().toString(); + + String result = doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isOk()); + String expected = "{\"payload\":\"{\\\"value1\\\":\\\"A\\\",\\\"value2\\\":\\\"B\\\"}\"}"; + latch.await(3, TimeUnit.SECONDS); + Assert.assertEquals(expected, result); + } + protected MqttMessage processMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException { MqttMessage message = new MqttMessage(); if (requestTopic.startsWith(MqttTopics.BASE_DEVICE_API_TOPIC)) { - TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() - .setPayload(DEVICE_RESPONSE) - .setRequestId(0) + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration(); + ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA); + DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); + + byte[] requestPayload = mqttMessage.getPayload(); + DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg"); + Descriptors.Descriptor rpcRequestMsgDescriptor = rpcRequestMsg.getDescriptorForType(); + assertNotNull(rpcRequestMsgDescriptor); + try { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload); + List fields = rpcRequestMsgDescriptor.getFields(); + for (Descriptors.FieldDescriptor fieldDescriptor: fields) { + assertTrue(dynamicMessage.hasField(fieldDescriptor)); + } + ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA); + DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); + + DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg"); + Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType(); + assertNotNull(rpcResponseMsgDescriptor); + DynamicMessage rpcResponseMsg = rpcResponseBuilder + .setField(rpcResponseMsgDescriptor.findFieldByName("payload"), DEVICE_RESPONSE) .build(); - message.setPayload(toDeviceRpcResponseMsg.toByteArray()); + message.setPayload(rpcResponseMsg.toByteArray()); + } catch (InvalidProtocolBufferException e) { + log.warn("Command Response Ack Error, Invalid response received: ", e); + } } else { TransportApiProtos.GatewayDeviceRpcRequestMsg msg = TransportApiProtos.GatewayDeviceRpcRequestMsg.parseFrom(mqttMessage.getPayload()); String deviceName = msg.getDeviceName(); @@ -105,6 +181,14 @@ public abstract class AbstractMqttServerSideRpcProtoIntegrationTest extends Abst return message; } + private ProtoTransportPayloadConfiguration getProtoTransportPayloadConfiguration() { + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); + MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + return (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + } } diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index 93a004318d..6140b7d8ed 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -119,7 +119,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac " }\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); + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, schemaStr, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); @@ -172,7 +172,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @Test 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); + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, null, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); TransportApiProtos.GatewayTelemetryMsg.Builder gatewayTelemetryMsgProtoBuilder = TransportApiProtos.GatewayTelemetryMsg.newBuilder(); List expectedKeys = Arrays.asList("key1", "key2", "key3", "key4", "key5"); String deviceName1 = "Device A"; @@ -186,7 +186,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac @Test 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); + super.processBeforeTest("Test Post Telemetry device proto payload", "Test Post Telemetry gateway proto payload", TransportPayloadType.PROTOBUF, POST_DATA_TELEMETRY_TOPIC, null, null, null, null, null, null, null, DeviceProfileProvisionType.DISABLED); 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/ProtoTransportPayloadConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index 13982f7e0b..159fbced16 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,7 +15,6 @@ */ 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; @@ -46,9 +45,13 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC 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 final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema"; + public static final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema"; private String deviceTelemetryProtoSchema; private String deviceAttributesProtoSchema; + private String deviceRpcRequestProtoSchema; + private String deviceRpcResponseProtoSchema; @Override public TransportPayloadType getTransportPayloadType() { @@ -63,13 +66,45 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC return getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA); } + public Descriptors.Descriptor getRpcResponseDynamicMessageDescriptor(String deviceRpcResponseProtoSchema) { + return getDescriptor(deviceRpcResponseProtoSchema, RPC_RESPONSE_PROTO_SCHEMA); + } + + public DynamicMessage.Builder getRpcRequestDynamicMessageBuilder(String deviceRpcRequestProtoSchema) { + return getDynamicMessageBuilder(deviceRpcRequestProtoSchema, RPC_REQUEST_PROTO_SCHEMA); + } + + public String getDeviceRpcResponseProtoSchema() { + if (!isEmptyStr(deviceRpcResponseProtoSchema)) { + return deviceRpcResponseProtoSchema; + } else { + return "syntax =\"proto3\";\n" + + "package rpc;\n" + + "\n" + + "message RpcResponseMsg {\n" + + " string payload = 1;\n" + + "}"; + } + } + + public String getDeviceRpcRequestProtoSchema() { + if (!isEmptyStr(deviceRpcRequestProtoSchema)) { + return deviceRpcRequestProtoSchema; + } else { + return "syntax =\"proto3\";\n" + + "package rpc;\n" + + "\n" + + "message RpcRequestMsg {\n" + + " string method = 1;\n" + + " int32 requestId = 2;\n" + + " string params = 3;\n" + + "}"; + } + } + private Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { 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); + DynamicMessage.Builder builder = getDynamicMessageBuilder(protoSchema, schemaName); return builder.getDescriptorForType(); } catch (Exception e) { log.warn("Failed to get Message Descriptor due to {}", e.getMessage()); @@ -77,6 +112,14 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC } } + public DynamicMessage.Builder getDynamicMessageBuilder(String protoSchema, String schemaName) { + ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); + String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream() + .map(MessageElement::getName).reduce((previous, last) -> last).get(); + return dynamicSchema.newMessageBuilder(lastMsgName); + } + public DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) { DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); schemaBuilder.setName(schemaName); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 8d756631e8..dcbb62e0c5 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.coap; import com.google.gson.JsonParseException; import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; @@ -261,7 +262,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionIdMap.get(getTokenFromRequest(request)); if (currentAttrSession == null) { attributeSubscriptions.add(sessionId); - registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request)); + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, + transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new CoapNoOpCallback(exchange)); } @@ -281,7 +283,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionIdMap.get(getTokenFromRequest(request)); if (currentRpcSession == null) { rpcSubscriptions.add(sessionId); - registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, getTokenFromRequest(request)); + registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, + transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.getDefaultInstance(), new CoapNoOpCallback(exchange)); @@ -303,17 +306,19 @@ public class CoapTransportResource extends AbstractCoapTransportResource { break; case TO_DEVICE_RPC_RESPONSE: transportService.process(sessionInfo, - coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request), + coapTransportAdaptor.convertToDeviceRpcResponse(sessionId, request, transportConfigurationContainer.getRpcResponseMsgDescriptor()), new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); break; case TO_SERVER_RPC_REQUEST: - transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), timeout); + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, + transportConfigurationContainer.getRpcRequestDynamicMessageBuilder()), timeout); transportService.process(sessionInfo, coapTransportAdaptor.convertToServerRpcRequest(sessionId, request), new CoapNoOpCallback(exchange)); break; case GET_ATTRIBUTES_REQUEST: - transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor), timeout); + transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, + transportConfigurationContainer.getRpcRequestDynamicMessageBuilder()), timeout); transportService.process(sessionInfo, coapTransportAdaptor.convertToGetAttributes(sessionId, request), new CoapNoOpCallback(exchange)); @@ -330,14 +335,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource { return tokenToSessionIdMap.remove(token); } - private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, String token) { + private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, String token) { tokenToSessionIdMap.putIfAbsent(token, sessionInfo); - transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor)); + transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder)); transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); } - private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor) { - return new CoapSessionListener(exchange, coapTransportAdaptor); + private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) { + return new CoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder); } private String getTokenFromRequest(Request request) { @@ -423,10 +428,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private final CoapExchange exchange; private final CoapTransportAdaptor coapTransportAdaptor; + private final DynamicMessage.Builder rpcRequestDynamicMessageBuilder; - CoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor) { + CoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) { this.exchange = exchange; this.coapTransportAdaptor = coapTransportAdaptor; + this.rpcRequestDynamicMessageBuilder = rpcRequestDynamicMessageBuilder; } @Override @@ -457,7 +464,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @Override public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { try { - exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg)); + exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg, rpcRequestDynamicMessageBuilder)); } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); @@ -545,9 +552,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource { (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; String deviceTelemetryProtoSchema = protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema(); String deviceAttributesProtoSchema = protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema(); + String deviceRpcRequestProtoSchema = protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema(); + String deviceRpcResponseProtoSchema = protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema(); return new TransportConfigurationContainer(false, protoTransportPayloadConfiguration.getTelemetryDynamicMessageDescriptor(deviceTelemetryProtoSchema), - protoTransportPayloadConfiguration.getAttributesDynamicMessageDescriptor(deviceAttributesProtoSchema)); + protoTransportPayloadConfiguration.getAttributesDynamicMessageDescriptor(deviceAttributesProtoSchema), + protoTransportPayloadConfiguration.getRpcResponseDynamicMessageDescriptor(deviceRpcResponseProtoSchema), + protoTransportPayloadConfiguration.getRpcRequestDynamicMessageBuilder(deviceRpcRequestProtoSchema) + ); } } else { throw new AdaptorException("Invalid CoapDeviceTypeConfiguration type: " + coapDeviceTypeConfiguration.getClass().getSimpleName() + "!"); @@ -567,11 +579,15 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private boolean jsonPayload; private Descriptors.Descriptor telemetryMsgDescriptor; private Descriptors.Descriptor attributesMsgDescriptor; + private Descriptors.Descriptor rpcResponseMsgDescriptor; + private DynamicMessage.Builder rpcRequestDynamicMessageBuilder; - public TransportConfigurationContainer(boolean jsonPayload, Descriptors.Descriptor telemetryMsgDescriptor, Descriptors.Descriptor attributesMsgDescriptor) { + public TransportConfigurationContainer(boolean jsonPayload, Descriptors.Descriptor telemetryMsgDescriptor, Descriptors.Descriptor attributesMsgDescriptor, Descriptors.Descriptor rpcResponseMsgDescriptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) { this.jsonPayload = jsonPayload; this.telemetryMsgDescriptor = telemetryMsgDescriptor; this.attributesMsgDescriptor = attributesMsgDescriptor; + this.rpcResponseMsgDescriptor = rpcResponseMsgDescriptor; + this.rpcRequestDynamicMessageBuilder = rpcRequestDynamicMessageBuilder; } public TransportConfigurationContainer(boolean jsonPayload) { diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java index 1d4703ead4..b30c86307b 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/CoapTransportAdaptor.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.coap.adaptors; import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.thingsboard.server.common.transport.adaptor.AdaptorException; @@ -32,7 +33,7 @@ public interface CoapTransportAdaptor { TransportProtos.GetAttributeRequestMsg convertToGetAttributes(UUID sessionId, Request inbound) throws AdaptorException; - TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException; + TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound, Descriptors.Descriptor rpcResponseMsgDescriptor) throws AdaptorException; TransportProtos.ToServerRpcRequestMsg convertToServerRpcRequest(UUID sessionId, Request inbound) throws AdaptorException; @@ -42,7 +43,7 @@ public interface CoapTransportAdaptor { Response convertToPublish(boolean isConfirmable, TransportProtos.AttributeUpdateNotificationMsg notificationMsg) throws AdaptorException; - Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException; + Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) throws AdaptorException; Response convertToPublish(TransportProtos.ToServerRpcResponseMsg msg) throws AdaptorException; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java index e382b2a3ee..85d35301c3 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/JsonCoapAdaptor.java @@ -20,6 +20,7 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; @@ -64,7 +65,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { } @Override - public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException { + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound, Descriptors.Descriptor rpcResponseMsgDescriptor) throws AdaptorException { Optional requestId = CoapTransportResource.getRequestId(inbound); String payload = validatePayload(sessionId, inbound, false); JsonObject response = new JsonParser().parse(payload).getAsJsonObject(); @@ -95,7 +96,7 @@ public class JsonCoapAdaptor implements CoapTransportAdaptor { } @Override - public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException { + public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg msg, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) throws AdaptorException { return getObserveNotification(isConfirmable, JsonConverter.toJson(msg, true)); } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java index d4c53fdc9a..120e49f697 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/adaptors/ProtoCoapAdaptor.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.coap.adaptors; +import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; @@ -63,16 +64,16 @@ public class ProtoCoapAdaptor implements CoapTransportAdaptor { } @Override - public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound) throws AdaptorException { + public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(UUID sessionId, Request inbound, Descriptors.Descriptor rpcResponseMsgDescriptor) throws AdaptorException { Optional requestId = CoapTransportResource.getRequestId(inbound); if (requestId.isEmpty()) { throw new AdaptorException("Request id is missing!"); } else { try { - String payload = TransportProtos.ToDeviceRpcResponseMsg.parseFrom(inbound.getPayload()).getPayload(); - return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.get()) - .setPayload(payload).build(); - } catch (InvalidProtocolBufferException e) { + JsonElement response = new JsonParser().parse(dynamicMsgToJson(inbound.getPayload(), rpcResponseMsgDescriptor)); + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId.orElseThrow(() -> new AdaptorException("Request id is missing!"))) + .setPayload(response.toString()).build(); + } catch (Exception e) { throw new AdaptorException(e); } } @@ -112,8 +113,8 @@ public class ProtoCoapAdaptor implements CoapTransportAdaptor { } @Override - public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg msg) throws AdaptorException { - return getObserveNotification(isConfirmable, msg.toByteArray()); + public Response convertToPublish(boolean isConfirmable, TransportProtos.ToDeviceRpcRequestMsg rpcRequest, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) throws AdaptorException { + return getObserveNotification(isConfirmable, ProtoConverter.convertToRpcRequest(rpcRequest, rpcRequestDynamicMessageBuilder)); } @Override 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 c948e755c2..30178bd44f 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,6 +15,7 @@ */ package org.thingsboard.server.transport.mqtt.adaptors; +import com.google.gson.JsonElement; import com.google.gson.JsonParser; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; @@ -64,9 +65,9 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { public TransportProtos.PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; byte[] bytes = toBytes(inbound.payload()); - Descriptors.Descriptor attributesDynamicMessage = getDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor()); + Descriptors.Descriptor attributesDynamicMessageDescriptor = getDescriptor(deviceSessionCtx.getAttributesDynamicMessageDescriptor()); try { - return JsonConverter.convertToAttributesProto(new JsonParser().parse(dynamicMsgToJson(bytes, attributesDynamicMessage))); + return JsonConverter.convertToAttributesProto(new JsonParser().parse(dynamicMsgToJson(bytes, attributesDynamicMessageDescriptor))); } catch (Exception e) { throw new AdaptorException(e); } @@ -86,8 +87,8 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { byte[] bytes = toBytes(inbound.payload()); String topicName = inbound.variableHeader().topicName(); - int requestId = getRequestId(topicName, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); try { + int requestId = getRequestId(topicName, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); return ProtoConverter.convertToGetAttributeRequestMessage(bytes, requestId); } catch (InvalidProtocolBufferException e) { log.warn("Failed to decode get attributes request", e); @@ -97,10 +98,15 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override public TransportProtos.ToDeviceRpcResponseMsg convertToDeviceRpcResponse(MqttDeviceAwareSessionContext ctx, MqttPublishMessage mqttMsg) throws AdaptorException { + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; + String topicName = mqttMsg.variableHeader().topicName(); byte[] bytes = toBytes(mqttMsg.payload()); + Descriptors.Descriptor rpcResponseDynamicMessageDescriptor = getDescriptor(deviceSessionCtx.getRpcResponseDynamicMessageDescriptor()); try { - return TransportProtos.ToDeviceRpcResponseMsg.parseFrom(bytes); - } catch (RuntimeException | InvalidProtocolBufferException e) { + int requestId = getRequestId(topicName, MqttTopics.DEVICE_RPC_RESPONSE_TOPIC); + JsonElement response = new JsonParser().parse(dynamicMsgToJson(bytes, rpcResponseDynamicMessageDescriptor)); + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(response.toString()).build(); + } catch (Exception e) { log.warn("Failed to decode Rpc response", e); throw new AdaptorException(e); } @@ -145,8 +151,10 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override - public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { - return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest))); + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) throws AdaptorException { + DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; + DynamicMessage.Builder rpcRequestDynamicMessageBuilder = deviceSessionCtx.getRpcRequestDynamicMessageBuilder(); + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), ProtoConverter.convertToRpcRequest(rpcRequest, rpcRequestDynamicMessageBuilder))); } @Override 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 efc5c057ed..3804d96cf6 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 @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.mqtt.session; import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; import io.netty.channel.ChannelHandlerContext; import lombok.Getter; import lombok.Setter; @@ -60,6 +61,8 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { private volatile TransportPayloadType payloadType = TransportPayloadType.JSON; private volatile Descriptors.Descriptor attributesDynamicMessageDescriptor; private volatile Descriptors.Descriptor telemetryDynamicMessageDescriptor; + private volatile Descriptors.Descriptor rpcResponseDynamicMessageDescriptor; + private volatile DynamicMessage.Builder rpcRequestDynamicMessageBuilder; @Getter @Setter @@ -102,6 +105,14 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { return attributesDynamicMessageDescriptor; } + public Descriptors.Descriptor getRpcResponseDynamicMessageDescriptor() { + return rpcResponseDynamicMessageDescriptor; + } + + public DynamicMessage.Builder getRpcRequestDynamicMessageBuilder() { + return rpcRequestDynamicMessageBuilder; + } + @Override public void setDeviceProfile(DeviceProfile deviceProfile) { super.setDeviceProfile(deviceProfile); @@ -136,5 +147,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext { ProtoTransportPayloadConfiguration protoTransportPayloadConfig = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; telemetryDynamicMessageDescriptor = protoTransportPayloadConfig.getTelemetryDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceTelemetryProtoSchema()); attributesDynamicMessageDescriptor = protoTransportPayloadConfig.getAttributesDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceAttributesProtoSchema()); + rpcResponseDynamicMessageDescriptor = protoTransportPayloadConfig.getRpcResponseDynamicMessageDescriptor(protoTransportPayloadConfig.getDeviceRpcResponseProtoSchema()); + rpcRequestDynamicMessageBuilder = protoTransportPayloadConfig.getRpcRequestDynamicMessageBuilder(protoTransportPayloadConfig.getDeviceRpcRequestProtoSchema()); } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java index 42c99bc428..d0d471a784 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/AdaptorException.java @@ -31,4 +31,8 @@ public class AdaptorException extends Exception { super(cause); } + public AdaptorException(String message, Exception cause) { + super(message, cause); + } + } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java index ed440ebcfe..620728cda4 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java @@ -15,10 +15,13 @@ */ package org.thingsboard.server.common.transport.adaptor; +import com.google.gson.Gson; import com.google.gson.JsonElement; +import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.google.gson.JsonPrimitive; +import com.google.protobuf.DynamicMessage; import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -34,6 +37,7 @@ import java.util.List; @Slf4j public class ProtoConverter { + public static final Gson GSON = new Gson(); public static final JsonParser JSON_PARSER = new JsonParser(); public static TransportProtos.PostTelemetryMsg convertToTelemetryProto(byte[] payload) throws InvalidProtocolBufferException, IllegalArgumentException { @@ -170,26 +174,20 @@ public class ProtoConverter { return kvList; } - public static byte[] convertToRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) { - TransportProtos.ToDeviceRpcRequestMsg.Builder toDeviceRpcRequestMsgBuilder = toDeviceRpcRequestMsg.newBuilderForType(); - toDeviceRpcRequestMsgBuilder.mergeFrom(toDeviceRpcRequestMsg); - toDeviceRpcRequestMsgBuilder.setParams(parseParams(toDeviceRpcRequestMsg)); - TransportProtos.ToDeviceRpcRequestMsg result = toDeviceRpcRequestMsgBuilder.build(); - return result.toByteArray(); - } - - private static String parseParams(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) { + public static byte[] convertToRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) throws AdaptorException { + rpcRequestDynamicMessageBuilder.clear(); + JsonObject rpcRequestJson = new JsonObject(); + rpcRequestJson.addProperty("method", toDeviceRpcRequestMsg.getMethodName()); + rpcRequestJson.addProperty("requestId", toDeviceRpcRequestMsg.getRequestId()); String params = toDeviceRpcRequestMsg.getParams(); - JsonElement jsonElementParams = JSON_PARSER.parse(params); - if (!jsonElementParams.isJsonPrimitive()) { - return params; - } else { - JsonPrimitive primitiveParams = jsonElementParams.getAsJsonPrimitive(); - if (jsonElementParams.getAsJsonPrimitive().isString()) { - return primitiveParams.getAsString(); - } else { - return params; - } + try { + JsonElement paramsElement = JSON_PARSER.parse(params); + rpcRequestJson.add("params", paramsElement); + JsonFormat.parser().ignoringUnknownFields().merge(GSON.toJson(rpcRequestJson), rpcRequestDynamicMessageBuilder); + DynamicMessage dynamicRpcRequest = rpcRequestDynamicMessageBuilder.build(); + return dynamicRpcRequest.toByteArray(); + } catch (Exception e) { + throw new AdaptorException("Failed to convert ToDeviceRpcRequestMsg to Dynamic Rpc request message due to: ", e); } } } 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 bff523d956..48879d2f15 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,8 @@ */ package org.thingsboard.server.dao.device; +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; @@ -87,6 +89,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D 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 final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema"; + private static final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema"; private static String invalidSchemaProvidedMessage(String schemaName) { return "[Transport Configuration] invalid " + schemaName + " provided!"; @@ -357,9 +361,10 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (transportConfiguration instanceof MqttDeviceProfileTransportConfiguration) { MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; if (mqttTransportConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration) { - ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration = + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); - validateProtoSchemas(protoTransportPayloadTypeConfiguration); + validateProtoSchemas(protoTransportPayloadConfiguration); + validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); } } else if (transportConfiguration instanceof CoapDeviceProfileTransportConfiguration) { CoapDeviceProfileTransportConfiguration coapDeviceProfileTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; @@ -370,6 +375,7 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration) { ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; validateProtoSchemas(protoTransportPayloadConfiguration); + validateRpcRequestDynamicMessageFields(protoTransportPayloadConfiguration); } } } @@ -417,6 +423,8 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D try { validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceAttributesProtoSchema(), ATTRIBUTES_PROTO_SCHEMA); validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(), TELEMETRY_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema(), RPC_REQUEST_PROTO_SCHEMA); + validateTransportProtoSchema(protoTransportPayloadTypeConfiguration.getDeviceRpcResponseProtoSchema(), RPC_RESPONSE_PROTO_SCHEMA); } catch (Exception exception) { throw new DataValidationException(exception.getMessage()); } @@ -539,6 +547,48 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D }; + private void validateRpcRequestDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { + DynamicMessage.Builder rpcRequestDynamicMessageBuilder = protoTransportPayloadTypeConfiguration.getRpcRequestDynamicMessageBuilder(protoTransportPayloadTypeConfiguration.getDeviceRpcRequestProtoSchema()); + Descriptors.Descriptor rpcRequestDynamicMessageDescriptor = rpcRequestDynamicMessageBuilder.getDescriptorForType(); + if (rpcRequestDynamicMessageDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get rpcRequestDynamicMessageDescriptor!"); + } else { + if (CollectionUtils.isEmpty(rpcRequestDynamicMessageDescriptor.getFields()) || rpcRequestDynamicMessageDescriptor.getFields().size() != 3) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " " + rpcRequestDynamicMessageDescriptor.getName() + " message should always contains 3 fields: method, requestId and params!"); + } + Descriptors.FieldDescriptor methodFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("method"); + if (methodFieldDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: method!"); + } else { + if (!Descriptors.FieldDescriptor.Type.STRING.equals(methodFieldDescriptor.getType())) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid data type. Only string type is supported!"); + } + if (methodFieldDescriptor.isRepeated()) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'method' has invalid label!"); + } + } + Descriptors.FieldDescriptor requestIdFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("requestId"); + if (requestIdFieldDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: requestId!"); + } else { + if (!Descriptors.FieldDescriptor.Type.INT32.equals(requestIdFieldDescriptor.getType())) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid data type. Only int32 type is supported!"); + } + if (requestIdFieldDescriptor.isRepeated()) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Field 'requestId' has invalid label!"); + } + } + Descriptors.FieldDescriptor paramsFieldDescriptor = rpcRequestDynamicMessageDescriptor.findFieldByName("params"); + if (paramsFieldDescriptor == null) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + " Failed to get field descriptor for field: params!"); + } else { + if (paramsFieldDescriptor.isRepeated()) { + throw new DataValidationException(invalidSchemaProvidedMessage(RPC_REQUEST_PROTO_SCHEMA) + "Field 'params' has invalid label!"); + } + } + } + } + private PaginatedRemover tenantDeviceProfilesRemover = new PaginatedRemover() { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html index e04ecfb258..320373e52c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.html @@ -61,6 +61,21 @@ {{ 'device-profile.attributes-proto-schema-required' | translate}} + + device-profile.rpc-request-proto-schema + + + {{ 'device-profile.rpc-request-proto-schema-required' | translate}} + + device-profile.rpc-request-proto-schema-hint + + + device-profile.rpc-response-proto-schema + + + {{ 'device-profile.rpc-response-proto-schema-required' | translate}} + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts index 4a686d39bd..dc1da3d973 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/coap-device-profile-transport-configuration.component.ts @@ -24,6 +24,8 @@ import { coapDeviceTypeTranslationMap, CoapTransportDeviceType, defaultAttributesSchema, + defaultRpcRequestSchema, + defaultRpcResponseSchema, defaultTelemetrySchema, DeviceProfileTransportConfiguration, DeviceTransportType, @@ -59,7 +61,9 @@ export class CoapDeviceProfileTransportConfigurationComponent implements Control private transportPayloadTypeConfiguration = this.fb.group({ transportPayloadType: [TransportPayloadType.JSON, Validators.required], deviceTelemetryProtoSchema: [defaultTelemetrySchema, Validators.required], - deviceAttributesProtoSchema: [defaultAttributesSchema, Validators.required] + deviceAttributesProtoSchema: [defaultAttributesSchema, Validators.required], + deviceRpcRequestProtoSchema: [defaultRpcRequestSchema, Validators.required], + deviceRpcResponseProtoSchema: [defaultRpcResponseSchema, Validators.required] }); get required(): boolean { 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 670e6fbb72..55f0869dbc 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 @@ -88,6 +88,21 @@ {{ 'device-profile.attributes-proto-schema-required' | translate}} + + device-profile.rpc-request-proto-schema + + + {{ 'device-profile.rpc-request-proto-schema-required' | translate}} + + device-profile.rpc-request-proto-schema-hint + + + device-profile.rpc-response-proto-schema + + + {{ 'device-profile.rpc-response-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 a61ad841aa..381e2f9e8d 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 @@ -29,6 +29,8 @@ import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { defaultAttributesSchema, + defaultRpcRequestSchema, + defaultRpcResponseSchema, defaultTelemetrySchema, DeviceProfileTransportConfiguration, DeviceTransportType, @@ -90,7 +92,9 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control transportPayloadTypeConfiguration: this.fb.group({ transportPayloadType: [TransportPayloadType.JSON, Validators.required], deviceTelemetryProtoSchema: [defaultTelemetrySchema, Validators.required], - deviceAttributesProtoSchema: [defaultAttributesSchema, Validators.required] + deviceAttributesProtoSchema: [defaultAttributesSchema, Validators.required], + deviceRpcRequestProtoSchema: [defaultRpcRequestSchema, Validators.required], + deviceRpcResponseProtoSchema: [defaultRpcResponseSchema, Validators.required] }) }, {validator: this.uniqueDeviceTopicValidator} ); @@ -139,15 +143,21 @@ export class MqttDeviceProfileTransportConfigurationComponent implements Control if (forceUpdated) { transportPayloadTypeForm.patchValue({ deviceTelemetryProtoSchema: defaultTelemetrySchema, - deviceAttributesProtoSchema: defaultAttributesSchema + deviceAttributesProtoSchema: defaultAttributesSchema, + deviceRpcRequestProtoSchema: defaultRpcRequestSchema, + deviceRpcResponseProtoSchema: defaultRpcResponseSchema }, {emitEvent: false}); } if (type === TransportPayloadType.PROTOBUF && !this.disabled) { transportPayloadTypeForm.get('deviceTelemetryProtoSchema').enable({emitEvent: false}); transportPayloadTypeForm.get('deviceAttributesProtoSchema').enable({emitEvent: false}); + transportPayloadTypeForm.get('deviceRpcRequestProtoSchema').enable({emitEvent: false}); + transportPayloadTypeForm.get('deviceRpcResponseProtoSchema').enable({emitEvent: false}); } else { transportPayloadTypeForm.get('deviceTelemetryProtoSchema').disable({emitEvent: false}); transportPayloadTypeForm.get('deviceAttributesProtoSchema').disable({emitEvent: false}); + transportPayloadTypeForm.get('deviceRpcRequestProtoSchema').enable({emitEvent: false}); + transportPayloadTypeForm.get('deviceRpcResponseProtoSchema').disable({emitEvent: false}); } } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 52990127bf..46b457a2ee 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -140,6 +140,24 @@ export const defaultAttributesSchema = ' string serialNumber = 2;\n' + '}'; +export const defaultRpcRequestSchema = + 'syntax ="proto3";\n' + + 'package rpc;\n' + + '\n' + + 'message RpcRequestMsg {\n' + + ' string method = 1;\n' + + ' int32 requestId = 2;\n' + + ' string params = 3;\n' + + '}'; + +export const defaultRpcResponseSchema = + 'syntax ="proto3";\n' + + 'package rpc;\n' + + '\n' + + 'message RpcResponseMsg {\n' + + ' string payload = 1;\n' + + '}'; + export const coapDeviceTypeTranslationMap = new Map( [ [CoapTransportDeviceType.DEFAULT, 'device-profile.coap-device-type-default'], @@ -170,6 +188,13 @@ export const deviceTransportTypeConfigurationInfoMap = new Map Date: Tue, 6 Apr 2021 17:05:34 +0300 Subject: [PATCH 040/127] Implement SNMP v3 security support, remove traps support --- .../SnmpDeviceTransportConfiguration.java | 23 +- .../snmp/AuthenticationProtocol.java | 45 ++ .../data/transport/snmp/PrivacyProtocol.java | 43 ++ .../transport/snmp/SnmpCommunicationSpec.java | 5 +- ...TrapsReceivingSnmpCommunicationConfig.java | 25 - .../snmp/configs/SnmpCommunicationConfig.java | 4 +- ...TrapsReceivingSnmpCommunicationConfig.java | 30 - .../transport/snmp/SnmpAuthService.java | 118 +++ .../transport/snmp/SnmpTransportContext.java | 12 +- .../snmp/service/SnmpTransportService.java | 142 ++-- .../snmp/session/DeviceSessionContext.java | 74 +- .../transport/snmp/SnmpDeviceSimulatorV2.java | 26 - .../transport/snmp/SnmpDeviceSimulatorV3.java | 723 +++++++++++++++++- .../snmp/{SnmpTest.java => SnmpTestV2.java} | 2 +- .../server/transport/snmp/SnmpTestV3.java | 46 ++ .../snmp-device-profile-transport-config.json | 10 - .../snmp-device-transport-config-v3.json | 13 + .../snmp-device-transport-config.json | 2 +- 18 files changed, 1057 insertions(+), 286 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java rename common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/{SnmpTest.java => SnmpTestV2.java} (98%) create mode 100644 common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV3.java create mode 100644 common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index a6056b4188..24ab463129 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -19,6 +19,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.transport.snmp.AuthenticationProtocol; +import org.thingsboard.server.common.data.transport.snmp.PrivacyProtocol; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; @Data @@ -26,9 +28,23 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur private String address; private int port; private SnmpProtocolVersion protocolVersion; + + /* + * For SNMP v1 and v2c + * */ + private String community; + + /* + * For SNMP v3 with User Based Security Model + * */ + private String username; private String securityName; - private String authenticationPassphrase; // for SNMP v3 - private String privacyPassphrase; // for SNMP v3 + private String contextName; + private AuthenticationProtocol authenticationProtocol; + private String authenticationPassphrase; + private PrivacyProtocol privacyProtocol; + private String privacyPassphrase; + private String engineId; @Override public DeviceTransportType getType() { @@ -44,7 +60,6 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur @JsonIgnore private boolean isValid() { - return StringUtils.isNotBlank(address) && port > 0 && - StringUtils.isNotBlank(securityName) && protocolVersion != null; + return true; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java new file mode 100644 index 0000000000..42d2e98798 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2021 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.transport.snmp; + +import java.util.Arrays; +import java.util.Optional; + +public enum AuthenticationProtocol { + SHA_1("1.3.6.1.6.3.10.1.1.3"), + SHA_224("1.3.6.1.6.3.10.1.1.4"), + SHA_256("1.3.6.1.6.3.10.1.1.5"), + SHA_384("1.3.6.1.6.3.10.1.1.6"), + SHA_512("1.3.6.1.6.3.10.1.1.7"), + MD5("1.3.6.1.6.3.10.1.1.2"); + + // oids taken from org.snmp4j.security.SecurityProtocol implementations + private final String oid; + + AuthenticationProtocol(String oid) { + this.oid = oid; + } + + public String getOid() { + return oid; + } + + public static Optional forName(String name) { + return Arrays.stream(values()) + .filter(protocol -> protocol.name().equalsIgnoreCase(name)) + .findFirst(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java new file mode 100644 index 0000000000..1c040eb83e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2021 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.transport.snmp; + +import java.util.Arrays; +import java.util.Optional; + +public enum PrivacyProtocol { + DES("1.3.6.1.6.3.10.1.2.2"), + AES_128("1.3.6.1.6.3.10.1.2.4"), + AES_192("1.3.6.1.4.1.4976.2.2.1.1.1"), + AES_256("1.3.6.1.4.1.4976.2.2.1.1.2"); + + // oids taken from org.snmp4j.security.SecurityProtocol implementations + private final String oid; + + PrivacyProtocol(String oid) { + this.oid = oid; + } + + public String getOid() { + return oid; + } + + public static Optional forName(String name) { + return Arrays.stream(values()) + .filter(protocol -> protocol.name().equalsIgnoreCase(name)) + .findFirst(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java index f6ff7d3109..a2edf75968 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java @@ -19,10 +19,7 @@ public enum SnmpCommunicationSpec { TELEMETRY_QUERYING(true), CLIENT_ATTRIBUTES_QUERYING(true), - SHARED_ATTRIBUTES_SETTING, - - TELEMETRY_TRAPS_RECEIVING, - CLIENT_ATTRIBUTES_TRAPS_RECEIVING; + SHARED_ATTRIBUTES_SETTING; private final boolean isRepeatingQuerying; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java deleted file mode 100644 index 9a11b01f1b..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright © 2016-2021 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.transport.snmp.configs; - -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; - -public class ClientAttributesTrapsReceivingSnmpCommunicationConfig extends SnmpCommunicationConfig { - @Override - public SnmpCommunicationSpec getSpec() { - return SnmpCommunicationSpec.CLIENT_ATTRIBUTES_TRAPS_RECEIVING; - } -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java index bfc194669b..7e1ee27237 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java @@ -31,9 +31,7 @@ import java.util.List; @JsonSubTypes({ @Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"), @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), - @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"), - @Type(value = TelemetryTrapsReceivingSnmpCommunicationConfig.class, name = "TELEMETRY_TRAPS_RECEIVING"), - @Type(value = ClientAttributesTrapsReceivingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_TRAPS_RECEIVING") + @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING") }) public abstract class SnmpCommunicationConfig { protected List mappings; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java deleted file mode 100644 index 66be4d0cf8..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2021 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.transport.snmp.configs; - -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; - -public class TelemetryTrapsReceivingSnmpCommunicationConfig extends SnmpCommunicationConfig { - @Override - public SnmpCommunicationSpec getSpec() { - return SnmpCommunicationSpec.TELEMETRY_TRAPS_RECEIVING; - } - - @Override - public boolean isValid() { - return false; - } -} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java new file mode 100644 index 0000000000..59b31d593c --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import lombok.RequiredArgsConstructor; +import org.snmp4j.AbstractTarget; +import org.snmp4j.CommunityTarget; +import org.snmp4j.Target; +import org.snmp4j.UserTarget; +import org.snmp4j.security.SecurityLevel; +import org.snmp4j.security.SecurityModel; +import org.snmp4j.security.SecurityProtocols; +import org.snmp4j.security.USM; +import org.snmp4j.security.UsmUser; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.service.SnmpTransportService; +import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; + +@Service +@TbSnmpTransportComponent +@RequiredArgsConstructor +public class SnmpAuthService { + private final SnmpTransportService snmpTransportService; + + @Value("${transport.snmp.underlying_protocol}") + private String snmpUnderlyingProtocol; + + public Target setUpSnmpTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + AbstractTarget target; + + SnmpProtocolVersion protocolVersion = deviceTransportConfig.getProtocolVersion(); + switch (protocolVersion) { + case V1: + CommunityTarget communityTargetV1 = new CommunityTarget(); + communityTargetV1.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv1); + communityTargetV1.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); + communityTargetV1.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); + target = communityTargetV1; + break; + case V2C: + CommunityTarget communityTargetV2 = new CommunityTarget(); + communityTargetV2.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c); + communityTargetV2.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); + communityTargetV2.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); + target = communityTargetV2; + break; + case V3: + OctetString username = new OctetString(deviceTransportConfig.getUsername()); + OctetString securityName = new OctetString(deviceTransportConfig.getSecurityName()); + OctetString engineId = new OctetString(deviceTransportConfig.getEngineId()); + + OID authenticationProtocol = new OID(deviceTransportConfig.getAuthenticationProtocol().getOid()); + OID privacyProtocol = new OID(deviceTransportConfig.getPrivacyProtocol().getOid()); + OctetString authenticationPassphrase = new OctetString(deviceTransportConfig.getAuthenticationPassphrase()); + authenticationPassphrase = new OctetString(SecurityProtocols.getInstance().passwordToKey(authenticationProtocol, authenticationPassphrase, engineId.getValue())); + OctetString privacyPassphrase = new OctetString(deviceTransportConfig.getPrivacyPassphrase()); + privacyPassphrase = new OctetString(SecurityProtocols.getInstance().passwordToKey(privacyProtocol, authenticationProtocol, privacyPassphrase, engineId.getValue())); + + USM usm = snmpTransportService.getSnmp().getUSM(); + if (usm.hasUser(engineId, securityName)) { + usm.removeAllUsers(username, engineId); + } + usm.addLocalizedUser( + engineId.getValue(), username, + authenticationProtocol, authenticationPassphrase.getValue(), + privacyProtocol, privacyPassphrase.getValue() + ); + + UserTarget userTarget = new UserTarget(); + userTarget.setSecurityName(securityName); + userTarget.setAuthoritativeEngineID(engineId.getValue()); + userTarget.setSecurityModel(SecurityModel.SECURITY_MODEL_USM); + userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV); + target = userTarget; + break; + default: + throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); + } + + target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); + target.setTimeout(profileTransportConfig.getTimeoutMs()); + target.setRetries(profileTransportConfig.getRetries()); + target.setVersion(protocolVersion.getCode()); + + return target; + } + + public void cleanUpSnmpAuthInfo(DeviceSessionContext sessionContext) { + SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); + if (deviceTransportConfiguration.getProtocolVersion() == SnmpProtocolVersion.V3) { + OctetString username = new OctetString(deviceTransportConfiguration.getUsername()); + OctetString engineId = new OctetString(deviceTransportConfiguration.getEngineId()); + snmpTransportService.getSnmp().getUSM().removeAllUsers(username, engineId); + } + } + +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 877d4f1652..2b0ce984aa 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.transport.snmp; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Device; @@ -61,18 +61,18 @@ import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor public class SnmpTransportContext extends TransportContext { + @Getter private final SnmpTransportService snmpTransportService; private final TransportDeviceProfileCache deviceProfileCache; private final TransportService transportService; private final ProtoTransportEntityService protoEntityService; private final SnmpTransportBalancingService balancingService; + @Getter + private final SnmpAuthService snmpAuthService; private final Map sessions = new ConcurrentHashMap<>(); private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); - @Value("${transport.snmp.underlying_protocol}") - private String snmpUnderlyingProtocol; - @AfterStartUp(order = 2) public void initDevicesSessions() { log.info("Initializing SNMP devices sessions"); @@ -116,8 +116,7 @@ public class SnmpTransportContext extends TransportContext { DeviceSessionContext deviceSessionContext = new DeviceSessionContext( device, deviceProfile, credentials.getCredentialsId(), - profileTransportConfiguration, deviceTransportConfiguration, - this, snmpTransportService, snmpUnderlyingProtocol + profileTransportConfiguration, deviceTransportConfiguration, this ); registerSessionMsgListener(deviceSessionContext); sessions.put(device.getId(), deviceSessionContext); @@ -155,6 +154,7 @@ public class SnmpTransportContext extends TransportContext { if (sessionContext == null) return; log.info("Destroying SNMP device session for device {}", sessionContext.getDevice().getId()); sessionContext.close(); + snmpAuthService.cleanUpSnmpAuthInfo(sessionContext); transportService.deregisterSession(sessionContext.getSessionInfo()); sessions.remove(sessionContext.getDeviceId()); snmpTransportService.cancelQueryingTasks(sessionContext); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 963b5db382..2f26bfa98c 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -19,29 +19,31 @@ import com.google.gson.JsonObject; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.snmp4j.CommandResponder; -import org.snmp4j.CommandResponderEvent; import org.snmp4j.PDU; +import org.snmp4j.ScopedPDU; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; import org.snmp4j.event.ResponseEvent; +import org.snmp4j.mp.MPv3; +import org.snmp4j.security.SecurityModels; +import org.snmp4j.security.SecurityProtocols; +import org.snmp4j.security.USM; import org.snmp4j.smi.Null; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; -import org.snmp4j.smi.TcpAddress; -import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.TbTransportService; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.data.transport.snmp.configs.RepeatingQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; @@ -49,12 +51,12 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; -import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; +import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; @@ -67,13 +69,11 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.stream.Collectors; -import java.util.stream.Stream; @TbSnmpTransportComponent @Service @Slf4j -public class SnmpTransportService implements TbTransportService, CommandResponder { - private final SnmpTransportContext snmpTransportContext; +public class SnmpTransportService implements TbTransportService { private final TransportService transportService; @Getter @@ -88,9 +88,7 @@ public class SnmpTransportService implements TbTransportService, CommandResponde @Value("${transport.snmp.underlying_protocol}") private String snmpUnderlyingProtocol; - public SnmpTransportService(@Lazy SnmpTransportContext snmpTransportContext, - TransportService transportService) { - this.snmpTransportContext = snmpTransportContext; + public SnmpTransportService(TransportService transportService) { this.transportService = transportService; } @@ -102,7 +100,6 @@ public class SnmpTransportService implements TbTransportService, CommandResponde responseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); initializeSnmp(); - initializeTrapsListener(); configureResponseProcessors(); log.info("SNMP transport service initialized"); @@ -122,29 +119,9 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } snmp = new Snmp(transportMapping); snmp.listen(); - } - - private void initializeTrapsListener() throws IOException { - int trapsListeningPort = 1062; - String bindingAddress = "0.0.0.0/" + trapsListeningPort; - TransportMapping transportMapping; - switch (snmpUnderlyingProtocol) { - case "udp": - transportMapping = new DefaultUdpTransportMapping(new UdpAddress(bindingAddress)); - break; - case "tcp": - transportMapping = new DefaultTcpTransportMapping(new TcpAddress(bindingAddress)); - break; - default: - throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported"); - } - - - Snmp trapsSnmp = new Snmp(transportMapping); - trapsSnmp.addCommandResponder(this); - - transportMapping.listen(); + USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); + SecurityModels.getInstance().addSecurityModel(usm); } public void createQueryingTasks(DeviceSessionContext sessionContext) { @@ -177,12 +154,11 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) throws IOException { - PDU request = createPdu(communicationConfig); - executeRequest(sessionContext, request); + sendRequest(sessionContext, communicationConfig, Collections.emptyMap()); } public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) throws IOException { - PDU request = createPduWithValues(communicationConfig, values); + PDU request = createPdu(sessionContext, communicationConfig, values); executeRequest(sessionContext, request); } @@ -193,46 +169,37 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } } - private PDU createPdu(SnmpCommunicationConfig communicationConfig) { - PDU pdu = new PDU(); - pdu.setType(communicationConfig.getMethod().getCode()); - pdu.addAll(communicationConfig.getMappings().stream() - .map(mapping -> new VariableBinding(new OID(mapping.getOid()))) - .collect(Collectors.toList())); - return pdu; - } + private PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + PDU pdu; + SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); + SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); + switch (snmpVersion) { + case V1: + case V2C: + pdu = new PDU(); + break; + case V3: + ScopedPDU scopedPdu = new ScopedPDU(); + scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); + scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); + pdu = scopedPdu; + break; + default: + throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); + } - private PDU createPduWithValues(SnmpCommunicationConfig communicationConfig, Map values) { - PDU pdu = new PDU(); pdu.setType(communicationConfig.getMethod().getCode()); pdu.addAll(communicationConfig.getMappings().stream() - .filter(mapping -> values.containsKey(mapping.getKey())) - .map(mapping -> { - String value = values.get(mapping.getKey()); - return new VariableBinding(new OID(mapping.getOid()), new OctetString(value)); - }) + .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) + .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) + .map(value -> new VariableBinding(new OID(mapping.getOid()), new OctetString(values.get(mapping.getKey())))) + .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) .collect(Collectors.toList())); + return pdu; } - private void processTrap(CommandResponderEvent event) { - if (event.getPDU().getType() != PDU.TRAP) return; - - snmpTransportContext.getSessions().stream() - .filter(sessionContext -> { - // TODO: SNMP v3 support - return sessionContext.getTarget().getSecurityName().equals(OctetString.fromByteArray(event.getSecurityName())) && - sessionContext.getTarget().getAddress().equals(event.getPeerAddress()); - }) - .findFirst() - .ifPresentOrElse(sessionContext -> { - responseProcessingExecutor.execute(() -> processResponse(sessionContext, event.getPDU())); - }, () -> { - log.debug("SNMP event is from unknown source: {}", event); - }); - } - public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) { ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); @@ -243,7 +210,7 @@ public class SnmpTransportService implements TbTransportService, CommandResponde PDU response = event.getResponse(); if (response == null) { - log.warn("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); + log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); return; } DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); @@ -308,23 +275,17 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } private void configureResponseProcessors() { - Stream.of(SnmpCommunicationSpec.TELEMETRY_QUERYING, SnmpCommunicationSpec.TELEMETRY_TRAPS_RECEIVING) - .forEach(telemetrySpec -> { - responseProcessors.put(telemetrySpec, (response, sessionContext) -> { - TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); - transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); - log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), response); - }); - }); - - Stream.of(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, SnmpCommunicationSpec.CLIENT_ATTRIBUTES_TRAPS_RECEIVING) - .forEach(clientAttributesSpec -> { - responseProcessors.put(clientAttributesSpec, (response, sessionContext) -> { - TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); - transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); - log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), response); - }); - }); + responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (response, sessionContext) -> { + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); + transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); + log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), response); + }); + + responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (response, sessionContext) -> { + TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); + transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); + log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), response); + }); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { @@ -353,11 +314,6 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } } - @Override - public void processPdu(CommandResponderEvent event) { - processTrap(event); - } - @Override public String getName() { return "SNMP"; diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index c0bbc843bc..c5a329547f 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -18,29 +18,15 @@ package org.thingsboard.server.transport.snmp.session; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.snmp4j.AbstractTarget; -import org.snmp4j.CommunityTarget; import org.snmp4j.Target; -import org.snmp4j.UserTarget; import org.snmp4j.event.ResponseEvent; import org.snmp4j.event.ResponseListener; -import org.snmp4j.security.AuthSHA; -import org.snmp4j.security.PrivDES; -import org.snmp4j.security.SecurityLevel; -import org.snmp4j.security.SecurityModel; -import org.snmp4j.security.SecurityModels; -import org.snmp4j.security.USM; -import org.snmp4j.security.UsmUser; -import org.snmp4j.smi.GenericAddress; -import org.snmp4j.smi.OID; -import org.snmp4j.smi.OctetString; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; @@ -50,6 +36,7 @@ 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 org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; +import org.thingsboard.server.transport.snmp.SnmpAuthService; import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.service.SnmpTransportService; @@ -77,6 +64,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final SnmpTransportContext snmpTransportContext; private final SnmpTransportService snmpTransportService; + private final SnmpAuthService snmpAuthService; @Getter @Setter @@ -84,7 +72,6 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final AtomicInteger msgIdSeq = new AtomicInteger(0); @Getter private boolean isActive = true; - private final String snmpUnderlyingProtocol; @Getter @Setter @@ -93,8 +80,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, SnmpDeviceTransportConfiguration deviceTransportConfiguration, - SnmpTransportContext snmpTransportContext, SnmpTransportService snmpTransportService, - String snmpUnderlyingProtocol) { + SnmpTransportContext snmpTransportContext) { super(UUID.randomUUID()); super.setDeviceId(device.getId()); super.setDeviceProfile(deviceProfile); @@ -102,12 +88,12 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S this.token = token; this.snmpTransportContext = snmpTransportContext; - this.snmpTransportService = snmpTransportService; + this.snmpTransportService = snmpTransportContext.getSnmpTransportService(); + this.snmpAuthService = snmpTransportContext.getSnmpAuthService(); this.profileTransportConfiguration = profileTransportConfiguration; this.deviceTransportConfiguration = deviceTransportConfiguration; - this.snmpUnderlyingProtocol = snmpUnderlyingProtocol; initializeTarget(profileTransportConfiguration, deviceTransportConfiguration); } @@ -133,55 +119,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { log.trace("Initializing target for SNMP session of device {}", device); - - AbstractTarget target; - - SnmpProtocolVersion protocolVersion = deviceTransportConfig.getProtocolVersion(); - switch (protocolVersion) { - case V1: - CommunityTarget communityTargetV1 = new CommunityTarget(); - communityTargetV1.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv1); - communityTargetV1.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); - communityTargetV1.setCommunity(new OctetString(deviceTransportConfig.getSecurityName())); - target = communityTargetV1; - break; - case V2C: - CommunityTarget communityTargetV2 = new CommunityTarget(); - communityTargetV2.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c); - communityTargetV2.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); - communityTargetV2.setCommunity(new OctetString(deviceTransportConfig.getSecurityName())); - target = communityTargetV2; - break; - case V3: - USM usm = new USM(); - SecurityModels.getInstance().addSecurityModel(usm); - - OctetString securityName = new OctetString(deviceTransportConfig.getSecurityName()); - OctetString authenticationPassphrase = new OctetString(deviceTransportConfig.getAuthenticationPassphrase()); - OctetString privacyPassphrase = new OctetString(deviceTransportConfig.getPrivacyPassphrase()); - - OID authenticationProtocol = AuthSHA.ID; - OID privacyProtocol = PrivDES.ID; // FIXME: to config - - UsmUser user = new UsmUser(securityName, authenticationProtocol, authenticationPassphrase, privacyProtocol, privacyPassphrase); - snmpTransportService.getSnmp().getUSM().addUser(user); - - UserTarget userTarget = new UserTarget(); - userTarget.setSecurityName(securityName); - userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV); - - target = userTarget; - break; - default: - throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); - } - - target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); - target.setTimeout(profileTransportConfig.getTimeoutMs()); - target.setRetries(profileTransportConfig.getRetries()); - target.setVersion(protocolVersion.getCode()); - - this.target = target; + this.target = snmpAuthService.setUpSnmpTarget(profileTransportConfig, deviceTransportConfig); log.info("SNMP target initialized: {}", target); } diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java index d48eaadce3..c97f47d573 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java @@ -73,32 +73,6 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent { } } - public static void main(String[] args) throws IOException { - SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public"); - - device.start(); - device.setUpMappings(Map.of( - ".1.3.6.1.2.1.1.1.50", "12", - ".1.3.6.1.2.1.2.1.52", "56", - ".1.3.6.1.2.1.3.1.54", "yes", - ".1.3.6.1.2.1.7.1.58", "" - )); - - -// while (true) { -// new Scanner(System.in).nextLine(); -// device.sendTrap("127.0.0.1", 1062, Map.of(".1.3.6.1.2.87.1.56", "12")); -// System.out.println("sent"); -// } - -// Snmp snmp = new Snmp(device.transportMappings[0]); -// device.snmp.addCommandResponder(event -> { -// System.out.println(event); -// }); - - new Scanner(System.in).nextLine(); - } - private final Target target; private final Address address; diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java index 63ccd7021a..da7f984267 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java @@ -15,38 +15,731 @@ */ package org.thingsboard.server.transport.snmp; -import org.snmp4j.UserTarget; +import org.snmp4j.MessageDispatcherImpl; +import org.snmp4j.TransportMapping; +import org.snmp4j.agent.BaseAgent; +import org.snmp4j.agent.CommandProcessor; +import org.snmp4j.agent.DuplicateRegistrationException; +import org.snmp4j.agent.MOGroup; +import org.snmp4j.agent.ManagedObject; +import org.snmp4j.agent.mo.DefaultMOMutableRow2PC; +import org.snmp4j.agent.mo.DefaultMOTable; +import org.snmp4j.agent.mo.MOAccessImpl; +import org.snmp4j.agent.mo.MOColumn; +import org.snmp4j.agent.mo.MOMutableColumn; +import org.snmp4j.agent.mo.MOMutableTableModel; +import org.snmp4j.agent.mo.MOScalar; +import org.snmp4j.agent.mo.MOTableIndex; +import org.snmp4j.agent.mo.MOTableRow; +import org.snmp4j.agent.mo.MOTableSubIndex; +import org.snmp4j.agent.mo.ext.AgentppSimulationMib; +import org.snmp4j.agent.mo.snmp.RowStatus; +import org.snmp4j.agent.mo.snmp.SnmpCommunityMIB; +import org.snmp4j.agent.mo.snmp.SnmpNotificationMIB; +import org.snmp4j.agent.mo.snmp.SnmpTargetMIB; +import org.snmp4j.agent.mo.snmp.StorageType; +import org.snmp4j.agent.mo.snmp.TransportDomains; +import org.snmp4j.agent.mo.snmp.VacmMIB; +import org.snmp4j.agent.mo.snmp4j.example.Snmp4jHeartbeatMib; +import org.snmp4j.agent.security.MutableVACM; +import org.snmp4j.mp.MPv1; +import org.snmp4j.mp.MPv2c; +import org.snmp4j.mp.MPv3; +import org.snmp4j.mp.MessageProcessingModel; +import org.snmp4j.security.AuthHMAC192SHA256; +import org.snmp4j.security.AuthMD5; import org.snmp4j.security.AuthSHA; +import org.snmp4j.security.PrivAES128; +import org.snmp4j.security.PrivAES192; +import org.snmp4j.security.PrivAES256; import org.snmp4j.security.PrivDES; import org.snmp4j.security.SecurityLevel; +import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityModels; +import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; import org.snmp4j.security.UsmUser; +import org.snmp4j.smi.Address; +import org.snmp4j.smi.Gauge32; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.Integer32; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.SMIConstants; +import org.snmp4j.smi.TcpAddress; +import org.snmp4j.smi.TimeTicks; +import org.snmp4j.smi.UdpAddress; +import org.snmp4j.smi.Variable; +import org.snmp4j.transport.DefaultTcpTransportMapping; +import org.snmp4j.transport.TransportMappings; +import org.snmp4j.util.ThreadPool; +import java.io.File; import java.io.IOException; +import java.util.Map; -public class SnmpDeviceSimulatorV3 extends SnmpDeviceSimulatorV2 { - public SnmpDeviceSimulatorV3(int port, String securityName, String authenticationPassphrase, String privacyPassphrase) throws IOException { - super(12, null); -// super(new File("conf.agent"), new File("bootCounter.agent")); +/** + * The TestAgent is a sample SNMP agent implementation of all + * features (MIB implementations) provided by the SNMP4J-Agent framework. + * + * Note, for snmp4s, this code is mostly a copy from snmp4j. + * And don't remove snmp users + * + */ +public class SnmpDeviceSimulatorV3 extends BaseAgent { + protected String address; + private Snmp4jHeartbeatMib heartbeatMIB; + private AgentppSimulationMib agentppSimulationMIB; + + public SnmpDeviceSimulatorV3(CommandProcessor processor) throws IOException { + super(new File("SNMP4JTestAgentBC.cfg"), new File("SNMP4JTestAgentConfig.cfg"), + processor); + agent.setWorkerPool(ThreadPool.create("RequestPool", 4)); + } + + public void setUpMappings(Map oidToResponseMappings) { + unregisterManagedObject(getSnmpv2MIB()); + oidToResponseMappings.forEach((oid, response) -> { + registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_WRITE, new OctetString(response))); + }); + } + protected void registerManagedObject(ManagedObject mo) { + try { + server.register(mo, null); + } catch (DuplicateRegistrationException ex) { + throw new RuntimeException(ex); + } + } + + protected void unregisterManagedObject(MOGroup moGroup) { + moGroup.unregisterMOs(server, getContext(moGroup)); + } + + protected void registerManagedObjects() { + try { + server.register(createStaticIfTable(), null); + server.register(createStaticIfXTable(), null); + agentppSimulationMIB.registerMOs(server, null); + heartbeatMIB.registerMOs(server, null); + } catch (DuplicateRegistrationException ex) { + ex.printStackTrace(); + } + } + + protected void addNotificationTargets(SnmpTargetMIB targetMIB, + SnmpNotificationMIB notificationMIB) { + targetMIB.addDefaultTDomains(); + + targetMIB.addTargetAddress(new OctetString("notificationV2c"), + TransportDomains.transportDomainUdpIpv4, + new OctetString(new UdpAddress("127.0.0.1/162").getValue()), + 200, 1, + new OctetString("notify"), + new OctetString("v2c"), + StorageType.permanent); + targetMIB.addTargetAddress(new OctetString("notificationV3"), + TransportDomains.transportDomainUdpIpv4, + new OctetString(new UdpAddress("127.0.0.1/1162").getValue()), + 200, 1, + new OctetString("notify"), + new OctetString("v3notify"), + StorageType.permanent); + targetMIB.addTargetParams(new OctetString("v2c"), + MessageProcessingModel.MPv2c, + SecurityModel.SECURITY_MODEL_SNMPv2c, + new OctetString("cpublic"), + SecurityLevel.AUTH_PRIV, + StorageType.permanent); + targetMIB.addTargetParams(new OctetString("v3notify"), + MessageProcessingModel.MPv3, + SecurityModel.SECURITY_MODEL_USM, + new OctetString("v3notify"), + SecurityLevel.NOAUTH_NOPRIV, + StorageType.permanent); + notificationMIB.addNotifyEntry(new OctetString("default"), + new OctetString("notify"), + SnmpNotificationMIB.SnmpNotifyTypeEnum.inform, + StorageType.permanent); + } + protected void addViews(VacmMIB vacm) { + vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv1, + new OctetString("cpublic"), + new OctetString("v1v2group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c, + new OctetString("cpublic"), + new OctetString("v1v2group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHADES"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("MD5DES"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("TEST"), + new OctetString("v3test"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHA"), + new OctetString("v3restricted"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHAAES128"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHAAES192"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHAAES256"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("MD5AES128"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("MD5AES192"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("MD5AES256"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("aboba"), + new OctetString("v3group"), + StorageType.nonVolatile); + //============================================// + // agent5-auth-priv + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("agent5"), + new OctetString("v3group"), + StorageType.nonVolatile); + //===========================================// + // agent002 + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("agent002"), + new OctetString("v3group"), + StorageType.nonVolatile); + //===========================================// + // user001-auth-no-priv + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("user001"), + new OctetString("group001"), + StorageType.nonVolatile); + //===========================================// + + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("v3notify"), + new OctetString("v3group"), + StorageType.nonVolatile); + + //===========================================// + // group auth no priv + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("v3notify-auth"), + new OctetString("group001"), + StorageType.nonVolatile); + //===========================================// + + + + // my conf + vacm.addAccess(new OctetString("group001"), new OctetString("public"), + SecurityModel.SECURITY_MODEL_USM, + SecurityLevel.AUTH_NOPRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("fullReadView"), + new OctetString("fullWriteView"), + new OctetString("fullNotifyView"), + StorageType.nonVolatile); + + vacm.addAccess(new OctetString("v1v2group"), new OctetString("public"), + SecurityModel.SECURITY_MODEL_ANY, + SecurityLevel.NOAUTH_NOPRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("fullReadView"), + new OctetString("fullWriteView"), + new OctetString("fullNotifyView"), + StorageType.nonVolatile); + vacm.addAccess(new OctetString("v3group"), new OctetString(), + SecurityModel.SECURITY_MODEL_USM, + SecurityLevel.AUTH_PRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("fullReadView"), + new OctetString("fullWriteView"), + new OctetString("fullNotifyView"), + StorageType.nonVolatile); + vacm.addAccess(new OctetString("v3restricted"), new OctetString(), + SecurityModel.SECURITY_MODEL_USM, + SecurityLevel.NOAUTH_NOPRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("restrictedReadView"), + new OctetString("restrictedWriteView"), + new OctetString("restrictedNotifyView"), + StorageType.nonVolatile); + vacm.addAccess(new OctetString("v3test"), new OctetString(), + SecurityModel.SECURITY_MODEL_USM, + SecurityLevel.AUTH_PRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("testReadView"), + new OctetString("testWriteView"), + new OctetString("testNotifyView"), + StorageType.nonVolatile); + + vacm.addViewTreeFamily(new OctetString("fullReadView"), new OID("1.3"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("fullWriteView"), new OID("1.3"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("fullNotifyView"), new OID("1.3"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + + vacm.addViewTreeFamily(new OctetString("restrictedReadView"), + new OID("1.3.6.1.2"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("restrictedWriteView"), + new OID("1.3.6.1.2.1"), + new OctetString(), + VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("restrictedNotifyView"), + new OID("1.3.6.1.2"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("restrictedNotifyView"), + new OID("1.3.6.1.6.3.1"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); - USM usm = new USM(); - SecurityModels.getInstance().addSecurityModel(usm); + vacm.addViewTreeFamily(new OctetString("testReadView"), + new OID("1.3.6.1.2"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("testReadView"), + new OID("1.3.6.1.2.1.1"), + new OctetString(), VacmMIB.vacmViewExcluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("testWriteView"), + new OID("1.3.6.1.2.1"), + new OctetString(), + VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("testNotifyView"), + new OID("1.3.6.1.2"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + } + + protected void addUsmUser(USM usm) { + UsmUser user = new UsmUser(new OctetString("SHADES"), + AuthSHA.ID, + new OctetString("SHADESAuthPassword"), + PrivDES.ID, + new OctetString("SHADESPrivPassword")); +// usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + usm.addUser(user.getSecurityName(), null, user); + user = new UsmUser(new OctetString("TEST"), + AuthSHA.ID, + new OctetString("maplesyrup"), + PrivDES.ID, + new OctetString("maplesyrup")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHA"), + AuthSHA.ID, + new OctetString("SHAAuthPassword"), + null, + null); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHADES"), + AuthSHA.ID, + new OctetString("SHADESAuthPassword"), + PrivDES.ID, + new OctetString("SHADESPrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("MD5DES"), + AuthMD5.ID, + new OctetString("MD5DESAuthPassword"), + PrivDES.ID, + new OctetString("MD5DESPrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHAAES128"), + AuthSHA.ID, + new OctetString("SHAAES128AuthPassword"), + PrivAES128.ID, + new OctetString("SHAAES128PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHAAES192"), + AuthSHA.ID, + new OctetString("SHAAES192AuthPassword"), + PrivAES192.ID, + new OctetString("SHAAES192PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHAAES256"), + AuthSHA.ID, + new OctetString("SHAAES256AuthPassword"), + PrivAES256.ID, + new OctetString("SHAAES256PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + + user = new UsmUser(new OctetString("MD5AES128"), + AuthMD5.ID, + new OctetString("MD5AES128AuthPassword"), + PrivAES128.ID, + new OctetString("MD5AES128PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("MD5AES192"), + AuthHMAC192SHA256.ID, + new OctetString("MD5AES192AuthPassword"), + PrivAES192.ID, + new OctetString("MD5AES192PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + //============================================================== + user = new UsmUser(new OctetString("MD5AES256"), + AuthMD5.ID, + new OctetString("MD5AES256AuthPassword"), + PrivAES256.ID, + new OctetString("MD5AES256PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("MD5AES256"), + AuthMD5.ID, + new OctetString("MD5AES256AuthPassword"), + PrivAES256.ID, + new OctetString("MD5AES256PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + + OctetString securityName = new OctetString("aboba"); + OctetString authenticationPassphrase = new OctetString("abobaaboba"); + OctetString privacyPassphrase = new OctetString("abobaaboba"); OID authenticationProtocol = AuthSHA.ID; - OID privacyProtocol = PrivDES.ID; + OID privacyProtocol = PrivDES.ID; // FIXME: to config + user = new UsmUser(securityName, authenticationProtocol, authenticationPassphrase, privacyProtocol, privacyPassphrase); + usm.addUser(user); + + //===============================================================// + user = new UsmUser(new OctetString("agent5"), + AuthSHA.ID, + new OctetString("authpass"), + PrivDES.ID, + new OctetString("privpass")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + //===============================================================// + // user001 + user = new UsmUser(new OctetString("user001"), + AuthSHA.ID, + new OctetString("authpass"), + null, null); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + //===============================================================// + // user002 + user = new UsmUser(new OctetString("user001"), + null, + null, + null, null); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + //===============================================================// + + user = new UsmUser(new OctetString("v3notify"), + null, + null, + null, + null); + usm.addUser(user.getSecurityName(), null, user); + + this.usm = usm; + } + + private static DefaultMOTable createStaticIfXTable() { + MOTableSubIndex[] subIndexes = + new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) }; + MOTableIndex indexDef = new MOTableIndex(subIndexes, false); + MOColumn[] columns = new MOColumn[19]; + int c = 0; + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, + MOAccessImpl.ACCESS_READ_ONLY); // ifName + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifInMulticastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifInBroadcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifOutMulticastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifOutBroadcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCInOctets + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCInUcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCInMulticastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCInBroadcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutOctets + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutUcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutMulticastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutBroadcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_WRITE); // ifLinkUpDownTrapEnable + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_GAUGE32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHighSpeed + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_WRITE); // ifPromiscuousMode + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifConnectorPresent + columns[c++] = + new MOMutableColumn(c, SMIConstants.SYNTAX_OCTET_STRING, // ifAlias + MOAccessImpl.ACCESS_READ_WRITE, null); + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_TIMETICKS, + MOAccessImpl.ACCESS_READ_ONLY); // ifCounterDiscontinuityTime + + DefaultMOTable ifXTable = + new DefaultMOTable(new OID("1.3.6.1.2.1.31.1.1.1"), indexDef, columns); + MOMutableTableModel model = (MOMutableTableModel) ifXTable.getModel(); + Variable[] rowValues1 = new Variable[] { + new OctetString("Ethernet-0"), + new Integer32(1), + new Integer32(2), + new Integer32(3), + new Integer32(4), + new Integer32(5), + new Integer32(6), + new Integer32(7), + new Integer32(8), + new Integer32(9), + new Integer32(10), + new Integer32(11), + new Integer32(12), + new Integer32(13), + new Integer32(14), + new Integer32(15), + new Integer32(16), + new OctetString("My eth"), + new TimeTicks(1000) + }; + Variable[] rowValues2 = new Variable[] { + new OctetString("Loopback"), + new Integer32(21), + new Integer32(22), + new Integer32(23), + new Integer32(24), + new Integer32(25), + new Integer32(26), + new Integer32(27), + new Integer32(28), + new Integer32(29), + new Integer32(30), + new Integer32(31), + new Integer32(32), + new Integer32(33), + new Integer32(34), + new Integer32(35), + new Integer32(36), + new OctetString("My loop"), + new TimeTicks(2000) + }; + model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1)); + model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2)); + ifXTable.setVolatile(true); + return ifXTable; + } + + private static DefaultMOTable createStaticIfTable() { + MOTableSubIndex[] subIndexes = + new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) }; + MOTableIndex indexDef = new MOTableIndex(subIndexes, false); + MOColumn[] columns = new MOColumn[8]; + int c = 0; + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifIndex + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, + MOAccessImpl.ACCESS_READ_ONLY); // ifDescr + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifType + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifMtu + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_GAUGE32, + MOAccessImpl.ACCESS_READ_ONLY); // ifSpeed + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, + MOAccessImpl.ACCESS_READ_ONLY); // ifPhysAddress + columns[c++] = + new MOMutableColumn(c, SMIConstants.SYNTAX_INTEGER, // ifAdminStatus + MOAccessImpl.ACCESS_READ_WRITE, null); + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifOperStatus - UsmUser user = new UsmUser(new OctetString(securityName), authenticationProtocol, new OctetString(authenticationPassphrase), privacyProtocol, new OctetString(privacyPassphrase)); + DefaultMOTable ifTable = + new DefaultMOTable(new OID("1.3.6.1.2.1.2.2.1"), indexDef, columns); + MOMutableTableModel model = (MOMutableTableModel) ifTable.getModel(); + Variable[] rowValues1 = new Variable[] { + new Integer32(1), + new OctetString("eth0"), + new Integer32(6), + new Integer32(1500), + new Gauge32(100000000), + new OctetString("00:00:00:00:01"), + new Integer32(1), + new Integer32(1) + }; + Variable[] rowValues2 = new Variable[] { + new Integer32(2), + new OctetString("loopback"), + new Integer32(24), + new Integer32(1500), + new Gauge32(10000000), + new OctetString("00:00:00:00:02"), + new Integer32(1), + new Integer32(1) + }; + model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1)); + model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2)); + ifTable.setVolatile(true); + return ifTable; } - public void initV3(UsmUser user, String securityName) { -// snmp.getUSM().addUser(user); + private static DefaultMOTable createStaticSnmp4sTable() { + MOTableSubIndex[] subIndexes = + new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) }; + MOTableIndex indexDef = new MOTableIndex(subIndexes, false); + MOColumn[] columns = new MOColumn[8]; + int c = 0; + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_NULL, MOAccessImpl.ACCESS_READ_ONLY); // testNull + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // testBoolean + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // ifType + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // ifMtu + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_GAUGE32, MOAccessImpl.ACCESS_READ_ONLY); // ifSpeed + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, MOAccessImpl.ACCESS_READ_ONLY); //ifPhysAddress + columns[c++] = new MOMutableColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_WRITE, + null); + // ifAdminStatus + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); + // ifOperStatus + + DefaultMOTable ifTable = + new DefaultMOTable(new OID("1.3.6.1.4.1.50000.1.1"), indexDef, columns); + MOMutableTableModel model = (MOMutableTableModel) ifTable.getModel(); + Variable[] rowValues1 = new Variable[] { + new Integer32(1), + new OctetString("eth0"), + new Integer32(6), + new Integer32(1500), + new Gauge32(100000000), + new OctetString("00:00:00:00:01"), + new Integer32(1), + new Integer32(1) + }; + Variable[] rowValues2 = new Variable[] { + new Integer32(2), + new OctetString("loopback"), + new Integer32(24), + new Integer32(1500), + new Gauge32(10000000), + new OctetString("00:00:00:00:02"), + new Integer32(1), + new Integer32(1) + }; + model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1)); + model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2)); + ifTable.setVolatile(true); + return ifTable; + } + + protected void initTransportMappings() throws IOException { + transportMappings = new TransportMapping[2]; + Address addr = GenericAddress.parse(address); + TransportMapping tm = + TransportMappings.getInstance().createTransportMapping(addr); + transportMappings[0] = tm; + transportMappings[1] = new DefaultTcpTransportMapping(new TcpAddress(address)); + } + + public void start(String ip, String port) throws IOException { + address = ip + "/" + port; + //BasicConfigurator.configure(); + init(); + addShutdownHook(); +// loadConfig(ImportModes.REPLACE_CREATE); + getServer().addContext(new OctetString("public")); + finishInit(); + run(); + sendColdStartNotification(); + } + + protected void unregisterManagedObjects() { + // here we should unregister those objects previously registered... + } + + protected void addCommunities(SnmpCommunityMIB communityMIB) { + Variable[] com2sec = new Variable[] { + new OctetString("public"), // community name + new OctetString("cpublic"), // security name + getAgent().getContextEngineID(), // local engine ID + new OctetString("public"), // default context name + new OctetString(), // transport tag + new Integer32(StorageType.nonVolatile), // storage type + new Integer32(RowStatus.active) // row status + }; + MOTableRow row = + communityMIB.getSnmpCommunityEntry().createRow( + new OctetString("public2public").toSubIndex(true), com2sec); + communityMIB.getSnmpCommunityEntry().addRow((SnmpCommunityMIB.SnmpCommunityEntryRow) row); +// snmpCommunityMIB.setSourceAddressFiltering(true); + } + + protected void registerSnmpMIBs() { + heartbeatMIB = new Snmp4jHeartbeatMib(super.getNotificationOriginator(), + new OctetString(), + super.snmpv2MIB.getSysUpTime()); + agentppSimulationMIB = new AgentppSimulationMib(); + super.registerSnmpMIBs(); + } - UserTarget userTarget = new UserTarget(); - userTarget.setSecurityName(new OctetString(securityName)); - userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV); + protected void initMessageDispatcher() { + this.dispatcher = new MessageDispatcherImpl(); + this.mpv3 = new MPv3(this.agent.getContextEngineID().getValue()); + this.usm = new USM(SecurityProtocols.getInstance(), this.agent.getContextEngineID(), this.updateEngineBoots()); + SecurityModels.getInstance().addSecurityModel(this.usm); + SecurityProtocols.getInstance().addDefaultProtocols(); + this.dispatcher.addMessageProcessingModel(new MPv1()); + this.dispatcher.addMessageProcessingModel(new MPv2c()); + this.dispatcher.addMessageProcessingModel(this.mpv3); + this.initSnmpSession(); } -} +} \ No newline at end of file diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java similarity index 98% rename from common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java rename to common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java index 29a8c085c8..417204296c 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.util.Map; import java.util.Scanner; -public class SnmpTest { +public class SnmpTestV2 { public static void main(String[] args) throws IOException { SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public"); diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV3.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV3.java new file mode 100644 index 0000000000..8e40deb17d --- /dev/null +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV3.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2021 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.snmp; + +import org.snmp4j.CommandResponderEvent; +import org.snmp4j.agent.CommandProcessor; +import org.snmp4j.mp.MPv3; +import org.snmp4j.smi.OctetString; + +import java.io.IOException; +import java.util.Map; +import java.util.Scanner; + +public class SnmpTestV3 { + public static void main(String[] args) throws IOException { + SnmpDeviceSimulatorV3 device = new SnmpDeviceSimulatorV3(new CommandProcessor(new OctetString(MPv3.createLocalEngineID())) { + @Override + public void processPdu(CommandResponderEvent event) { + System.out.println("event: " + event); + } + }); + device.start("0.0.0.0", "1610"); + + device.setUpMappings(Map.of( + ".1.3.6.1.2.1.1.1.50", "12", + ".1.3.6.1.2.1.2.1.52", "56", + ".1.3.6.1.2.1.3.1.54", "yes", + ".1.3.6.1.2.1.7.1.58", "" + )); + + new Scanner(System.in).nextLine(); + } +} diff --git a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json index f8291a090b..f74ebca0bf 100644 --- a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json +++ b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json @@ -38,16 +38,6 @@ "dataType": "STRING" } ] - }, - { - "spec": "TELEMETRY_TRAPS_RECEIVING", - "mappings": [ - { - "oid": ".1.3.6.1.2.8.7.1.56", - "key": "temperature_trap", - "dataType": "LONG" - } - ] } ] } diff --git a/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json b/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json new file mode 100644 index 0000000000..039e03fa53 --- /dev/null +++ b/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json @@ -0,0 +1,13 @@ +{ + "address": "192.168.3.23", + "port": 1610, + "protocolVersion": "V3", + + "username": "tb-user", + "engineId": "qwertyuioa", + "securityName": "tb-user", + "authenticationProtocol": "SHA_512", + "authenticationPassphrase": "sdfghjkloifgh", + "privacyProtocol": "DES", + "privacyPassphrase": "rtytguijokod" +} \ No newline at end of file diff --git a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json index 9a97b75e40..c73d817bfb 100644 --- a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json +++ b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json @@ -1,6 +1,6 @@ { "address": "127.0.0.1", "port": 1610, - "password": "public", + "community": "public", "protocolVersion": "V2C" } \ No newline at end of file From 415bf570bac6b422f880622163924464d6ca390b Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 7 Apr 2021 19:22:28 +0300 Subject: [PATCH 041/127] Refactor --- .../SnmpDeviceTransportConfiguration.java | 26 +++++++-- ...mpDeviceProfileTransportConfiguration.java | 18 +++++-- ...eatingQueryingSnmpCommunicationConfig.java | 2 +- ...ributesSettingSnmpCommunicationConfig.java | 5 -- .../snmp/configs/SnmpCommunicationConfig.java | 8 +-- .../transport/snmp/SnmpAuthService.java | 3 +- .../transport/snmp/SnmpTransportContext.java | 54 ++++++++++--------- .../snmp/service/SnmpTransportService.java | 27 ++++++++-- .../snmp/session/DeviceSessionContext.java | 14 ++--- 9 files changed, 98 insertions(+), 59 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index 24ab463129..68a9d1f218 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -17,16 +17,21 @@ package org.thingsboard.server.common.data.device.data; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import lombok.ToString; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.transport.snmp.AuthenticationProtocol; import org.thingsboard.server.common.data.transport.snmp.PrivacyProtocol; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; +import java.util.Objects; + @Data +@ToString(of = {"host", "port", "protocolVersion"}) public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration { - private String address; - private int port; + private String host; + private Integer port; private SnmpProtocolVersion protocolVersion; /* @@ -60,6 +65,21 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur @JsonIgnore private boolean isValid() { - return true; + boolean isValid = StringUtils.isNotBlank(host) && port != null && protocolVersion != null; + if (isValid) { + switch (protocolVersion) { + case V1: + case V2C: + isValid = StringUtils.isNotEmpty(community); + break; + case V3: + isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName) + && contextName != null && authenticationProtocol != null + && StringUtils.isNotBlank(authenticationPassphrase) + && privacyProtocol != null && privacyPassphrase != null && engineId != null; + break; + } + } + return isValid; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index 843a51304e..5d6afc7155 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -17,15 +17,21 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import org.apache.commons.lang3.ArrayUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; +import java.util.Collections; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Data public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - private int timeoutMs; - private int retries; + private Integer timeoutMs; + private Integer retries; private List communicationConfigs; @Override @@ -36,12 +42,16 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra @Override public void validate() { if (!isValid()) { - throw new IllegalArgumentException("Transport configuration is not valid"); + throw new IllegalArgumentException("SNMP transport configuration is not valid"); } } @JsonIgnore private boolean isValid() { - return true; + return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 + && communicationConfigs != null && !communicationConfigs.isEmpty() + && communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) + && communicationConfigs.stream().flatMap(config -> config.getMappings().stream()).map(SnmpMapping::getOid) + .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getMappings().size()).sum(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java index 764bf97b15..19a009457a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java @@ -31,6 +31,6 @@ public abstract class RepeatingQueryingSnmpCommunicationConfig extends SnmpCommu @Override public boolean isValid() { - return true; + return super.isValid() && queryingFrequencyMs != null && queryingFrequencyMs > 0; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java index 1c63dae618..74c67f957a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java @@ -28,9 +28,4 @@ public class SharedAttributesSettingSnmpCommunicationConfig extends SnmpCommunic public SnmpMethod getMethod() { return SnmpMethod.SET; } - - @Override - public boolean isValid() { - return true; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java index 7e1ee27237..acc0df0dd7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java @@ -49,12 +49,6 @@ public abstract class SnmpCommunicationConfig { @JsonIgnore public boolean isValid() { - return true; - } - - public void validate() { - if (!isValid()) { - throw new IllegalArgumentException("Communication config is not valid"); - } + return mappings != null && !mappings.isEmpty() && mappings.stream().allMatch(mapping -> mapping != null && mapping.isValid()); } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java index 59b31d593c..81187fb33e 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java @@ -24,7 +24,6 @@ import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; -import org.snmp4j.security.UsmUser; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; @@ -98,7 +97,7 @@ public class SnmpAuthService { throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); } - target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); + target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getHost() + "/" + deviceTransportConfig.getPort())); target.setTimeout(profileTransportConfig.getTimeoutMs()); target.setRetries(profileTransportConfig.getRetries()); target.setVersion(protocolVersion.getCode()); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 2b0ce984aa..d7441c7c1d 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -89,18 +89,12 @@ public class SnmpTransportContext extends TransportContext { managedDevicesIds.stream() .map(protoEntityService::getDeviceById) .collect(Collectors.toList()) - .forEach(device -> { - try { - establishDeviceSession(device); - } catch (Exception e) { - log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.getMessage()); - } - }); + .forEach(this::establishDeviceSession); } private void establishDeviceSession(Device device) { if (device == null) return; - log.info("Establishing SNMP device session for device {}", device.getId()); + log.info("Establishing SNMP session for device {}", device.getId()); DeviceProfileId deviceProfileId = device.getDeviceProfileId(); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); @@ -114,18 +108,24 @@ public class SnmpTransportContext extends TransportContext { SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - DeviceSessionContext deviceSessionContext = new DeviceSessionContext( - device, deviceProfile, credentials.getCredentialsId(), - profileTransportConfiguration, deviceTransportConfiguration, this - ); - registerSessionMsgListener(deviceSessionContext); + DeviceSessionContext deviceSessionContext; + try { + deviceSessionContext = new DeviceSessionContext( + device, deviceProfile, credentials.getCredentialsId(), + profileTransportConfiguration, deviceTransportConfiguration, this + ); + registerSessionMsgListener(deviceSessionContext); + } catch (Exception e) { + log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.getMessage()); + return; + } sessions.put(device.getId(), deviceSessionContext); snmpTransportService.createQueryingTasks(deviceSessionContext); log.info("Established SNMP device session for device {}", device.getId()); } private void updateDeviceSession(DeviceSessionContext sessionContext, Device device, DeviceProfile deviceProfile) { - log.info("Updating SNMP device session for device {}", device.getId()); + log.info("Updating SNMP session for device {}", device.getId()); DeviceCredentials credentials = protoEntityService.getDeviceCredentialsByDeviceId(device.getId()); if (credentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { @@ -137,16 +137,20 @@ public class SnmpTransportContext extends TransportContext { SnmpDeviceProfileTransportConfiguration newProfileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceTransportConfiguration newDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) { - sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration); - sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); - snmpTransportService.cancelQueryingTasks(sessionContext); - snmpTransportService.createQueryingTasks(sessionContext); - } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { - sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); - sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); - } else { - log.trace("Configuration of the device {} was not updated", device); + try { + if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) { + sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration); + sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + snmpTransportService.cancelQueryingTasks(sessionContext); + snmpTransportService.createQueryingTasks(sessionContext); + } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { + sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); + sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + } else { + log.trace("Configuration of the device {} was not updated", device); + } + } catch (Exception e) { + log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage()); } } @@ -156,8 +160,8 @@ public class SnmpTransportContext extends TransportContext { sessionContext.close(); snmpAuthService.cleanUpSnmpAuthInfo(sessionContext); transportService.deregisterSession(sessionContext.getSessionInfo()); - sessions.remove(sessionContext.getDeviceId()); snmpTransportService.cancelQueryingTasks(sessionContext); + sessions.remove(sessionContext.getDeviceId()); log.trace("Unregistered and removed session"); } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 2f26bfa98c..94a18f3d54 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -28,9 +28,11 @@ import org.snmp4j.mp.MPv3; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; +import org.snmp4j.smi.Integer32; import org.snmp4j.smi.Null; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.Variable; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; @@ -143,7 +145,7 @@ public class SnmpTransportService implements TbTransportService { sendRequest(sessionContext, communicationConfig); } } catch (Exception e) { - log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.getMessage()); + log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); } }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); } @@ -192,7 +194,24 @@ public class SnmpTransportService implements TbTransportService { pdu.addAll(communicationConfig.getMappings().stream() .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) - .map(value -> new VariableBinding(new OID(mapping.getOid()), new OctetString(values.get(mapping.getKey())))) + .map(value -> { + Variable variable; + switch (mapping.getDataType()) { + case LONG: + try { + variable = new Integer32(Integer.parseInt(value)); + break; + } catch (NumberFormatException ignored) { + } + case DOUBLE: + case BOOLEAN: + case STRING: + case JSON: + default: + variable = new OctetString(value); + } + return new VariableBinding(new OID(mapping.getOid()), variable); + }) .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) .collect(Collectors.toList())); @@ -267,7 +286,9 @@ public class SnmpTransportService implements TbTransportService { responses.forEach((spec, response) -> { Optional.ofNullable(responseProcessors.get(spec)) .ifPresent(responseProcessor -> { - responseProcessor.accept(response, sessionContext); + if (!response.entrySet().isEmpty()) { + responseProcessor.accept(response, sessionContext); + } }); }); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index c5a329547f..b3a38545a8 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -63,8 +63,6 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final Device device; private final SnmpTransportContext snmpTransportContext; - private final SnmpTransportService snmpTransportService; - private final SnmpAuthService snmpAuthService; @Getter @Setter @@ -80,7 +78,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, SnmpDeviceTransportConfiguration deviceTransportConfiguration, - SnmpTransportContext snmpTransportContext) { + SnmpTransportContext snmpTransportContext) throws Exception { super(UUID.randomUUID()); super.setDeviceId(device.getId()); super.setDeviceProfile(deviceProfile); @@ -88,8 +86,6 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S this.token = token; this.snmpTransportContext = snmpTransportContext; - this.snmpTransportService = snmpTransportContext.getSnmpTransportService(); - this.snmpAuthService = snmpTransportContext.getSnmpAuthService(); this.profileTransportConfiguration = profileTransportConfiguration; this.deviceTransportConfiguration = deviceTransportConfiguration; @@ -113,13 +109,13 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onResponse(ResponseEvent event) { if (isActive) { - snmpTransportService.processResponseEvent(this, event); + snmpTransportContext.getSnmpTransportService().processResponseEvent(this, event); } } - public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) throws Exception { log.trace("Initializing target for SNMP session of device {}", device); - this.target = snmpAuthService.setUpSnmpTarget(profileTransportConfig, deviceTransportConfig); + this.target = snmpTransportContext.getSnmpAuthService().setUpSnmpTarget(profileTransportConfig, deviceTransportConfig); log.info("SNMP target initialized: {}", target); } @@ -152,7 +148,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() )); try { - snmpTransportService.sendRequest(this, communicationConfig, sharedAttributes); + snmpTransportContext.getSnmpTransportService().sendRequest(this, communicationConfig, sharedAttributes); } catch (Exception e) { log.error("Failed to send request with shared attributes to SNMP device {}: {}", getDeviceId(), e.getMessage()); } From 783b959577be49cbf020f379e09a0043dfcd95a9 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 14 Apr 2021 15:51:13 +0300 Subject: [PATCH 042/127] Refactor --- ...mpDeviceProfileTransportConfiguration.java | 11 +- .../transport/snmp/SnmpCommunicationSpec.java | 21 +- .../data/transport/snmp/SnmpMapping.java | 7 +- ...ltipleMappingsSnmpCommunicationConfig.java | 36 +++ ...eatingQueryingSnmpCommunicationConfig.java | 6 +- .../SnmpCommunicationConfig.java | 24 +- ...ibutesQueryingSnmpCommunicationConfig.java | 5 +- ...ributesSettingSnmpCommunicationConfig.java | 7 +- ...emetryQueryingSnmpCommunicationConfig.java | 5 +- ...CommandSettingSnmpCommunicationConfig.java | 59 +++++ ...sponseQueryingSnmpCommunicationConfig.java | 60 +++++ .../transport/snmp/SnmpTransportContext.java | 7 +- .../transport/snmp/service/PduMapper.java | 150 +++++++++++ .../snmp/{ => service}/SnmpAuthService.java | 8 +- .../snmp/service/SnmpTransportService.java | 244 ++++++------------ .../snmp/session/DeviceSessionContext.java | 38 +-- 16 files changed, 454 insertions(+), 234 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config}/RepeatingQueryingSnmpCommunicationConfig.java (88%) rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config}/SnmpCommunicationConfig.java (75%) rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config/impl}/ClientAttributesQueryingSnmpCommunicationConfig.java (89%) rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config/impl}/SharedAttributesSettingSnmpCommunicationConfig.java (87%) rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config/impl}/TelemetryQueryingSnmpCommunicationConfig.java (90%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java rename common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/{ => service}/SnmpAuthService.java (94%) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index 5d6afc7155..bbf074eeda 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -17,16 +17,11 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import org.apache.commons.lang3.ArrayUtils; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; -import java.util.Collections; import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Data public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { @@ -51,7 +46,7 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 && communicationConfigs != null && !communicationConfigs.isEmpty() && communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) - && communicationConfigs.stream().flatMap(config -> config.getMappings().stream()).map(SnmpMapping::getOid) - .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getMappings().size()).sum(); + && communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid) + .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java index a2edf75968..a6643ecf1e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java @@ -16,22 +16,11 @@ package org.thingsboard.server.common.data.transport.snmp; public enum SnmpCommunicationSpec { - TELEMETRY_QUERYING(true), - CLIENT_ATTRIBUTES_QUERYING(true), + TELEMETRY_QUERYING, - SHARED_ATTRIBUTES_SETTING; + CLIENT_ATTRIBUTES_QUERYING, + SHARED_ATTRIBUTES_SETTING, - private final boolean isRepeatingQuerying; - - SnmpCommunicationSpec() { - this.isRepeatingQuerying = false; - } - - SnmpCommunicationSpec(boolean isRepeatingQuerying) { - this.isRepeatingQuerying = isRepeatingQuerying; - } - - public boolean isRepeatingQuerying() { - return isRepeatingQuerying; - } + TO_DEVICE_RPC_COMMAND_SETTING, + TO_DEVICE_RPC_RESPONSE_QUERYING } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java index 0269491461..d787f7446f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java @@ -16,13 +16,17 @@ package org.thingsboard.server.common.data.transport.snmp; import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.kv.DataType; import java.util.regex.Pattern; @Data +@AllArgsConstructor +@NoArgsConstructor public class SnmpMapping { private String oid; private String key; @@ -32,7 +36,6 @@ public class SnmpMapping { @JsonIgnore public boolean isValid() { - return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && - StringUtils.isNotBlank(key) && dataType != null; + return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && StringUtils.isNotBlank(key); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java new file mode 100644 index 0000000000..f785c8788d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.config; + +import lombok.Data; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; + +import java.util.List; + +@Data +public abstract class MultipleMappingsSnmpCommunicationConfig implements SnmpCommunicationConfig { + protected List mappings; + + @Override + public boolean isValid() { + return mappings != null && !mappings.isEmpty() && mappings.stream().allMatch(mapping -> mapping != null && mapping.isValid()); + } + + @Override + public List getAllMappings() { + return mappings; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/RepeatingQueryingSnmpCommunicationConfig.java similarity index 88% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/RepeatingQueryingSnmpCommunicationConfig.java index 19a009457a..38451d4142 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/RepeatingQueryingSnmpCommunicationConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config; import lombok.Data; import lombok.EqualsAndHashCode; @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; @EqualsAndHashCode(callSuper = true) @Data -public abstract class RepeatingQueryingSnmpCommunicationConfig extends SnmpCommunicationConfig { +public abstract class RepeatingQueryingSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig { private Long queryingFrequencyMs; @Override @@ -31,6 +31,6 @@ public abstract class RepeatingQueryingSnmpCommunicationConfig extends SnmpCommu @Override public boolean isValid() { - return super.isValid() && queryingFrequencyMs != null && queryingFrequencyMs > 0; + return queryingFrequencyMs != null && queryingFrequencyMs > 0 && super.isValid(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java similarity index 75% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java index acc0df0dd7..e2ba29abfa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -23,6 +23,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; +import org.thingsboard.server.common.data.transport.snmp.config.impl.ClientAttributesQueryingSnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.SharedAttributesSettingSnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig; import java.util.List; @@ -33,22 +36,19 @@ import java.util.List; @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING") }) -public abstract class SnmpCommunicationConfig { - protected List mappings; +public interface SnmpCommunicationConfig { - public List getMappings() { - return mappings; - } - - public abstract SnmpCommunicationSpec getSpec(); + SnmpCommunicationSpec getSpec(); @JsonIgnore - public SnmpMethod getMethod() { + default SnmpMethod getMethod() { return null; } @JsonIgnore - public boolean isValid() { - return mappings != null && !mappings.isEmpty() && mappings.stream().allMatch(mapping -> mapping != null && mapping.isValid()); - } + List getAllMappings(); + + @JsonIgnore + boolean isValid(); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java similarity index 89% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java index 16198f8ef6..51bd2198e9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config.impl; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; public class ClientAttributesQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java similarity index 87% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java index 74c67f957a..441395d279 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config.impl; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; +import org.thingsboard.server.common.data.transport.snmp.config.MultipleMappingsSnmpCommunicationConfig; + +public class SharedAttributesSettingSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig { -public class SharedAttributesSettingSnmpCommunicationConfig extends SnmpCommunicationConfig { @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING; @@ -28,4 +30,5 @@ public class SharedAttributesSettingSnmpCommunicationConfig extends SnmpCommunic public SnmpMethod getMethod() { return SnmpMethod.SET; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java similarity index 90% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java index 68d3c81ed2..c3f82d64ec 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java @@ -13,17 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config.impl; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; @EqualsAndHashCode(callSuper = true) @Data public class TelemetryQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.TELEMETRY_QUERYING; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..97f3de047b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.config.impl; + +import lombok.Data; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Data +public class ToDeviceRpcCommandSettingSnmpCommunicationConfig implements SnmpCommunicationConfig { + private SnmpMapping mapping; + + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.TO_DEVICE_RPC_COMMAND_SETTING; + } + + @Override + public SnmpMethod getMethod() { + return SnmpMethod.SET; + } + + public void setMapping(SnmpMapping mapping) { + this.mapping = mapping != null ? new SnmpMapping(mapping.getOid(), RPC_COMMAND_KEY_NAME, DataType.STRING) : null; + } + + @Override + public List getAllMappings() { + return Collections.singletonList(mapping); + } + + @Override + public boolean isValid() { + return mapping != null && mapping.isValid(); + } + + public static final String RPC_COMMAND_KEY_NAME = "rpcCommand"; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..18bce94980 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.config.impl; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; + +import java.util.Collections; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ToDeviceRpcResponseQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + private SnmpMapping mapping; + + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.TO_DEVICE_RPC_RESPONSE_QUERYING; + } + + @Override + public SnmpMethod getMethod() { + return SnmpMethod.GET; + } + + public void setMapping(SnmpMapping mapping) { + this.mapping = mapping != null ? new SnmpMapping(mapping.getOid(), RPC_RESPONSE_KEY_NAME, DataType.STRING) : null; + } + + @Override + public List getAllMappings() { + return Collections.singletonList(mapping); + } + + @Override + public boolean isValid() { + return true; + } + + public static final String RPC_RESPONSE_KEY_NAME = "rpcResponse"; + +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index d7441c7c1d..d66d09ab0b 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.service.ProtoTransportEntityService; +import org.thingsboard.server.transport.snmp.service.SnmpAuthService; import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingService; import org.thingsboard.server.transport.snmp.service.SnmpTransportService; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; @@ -116,7 +117,7 @@ public class SnmpTransportContext extends TransportContext { ); registerSessionMsgListener(deviceSessionContext); } catch (Exception e) { - log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.getMessage()); + log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.toString()); return; } sessions.put(device.getId(), deviceSessionContext); @@ -177,7 +178,9 @@ public class SnmpTransportContext extends TransportContext { ); transportService.registerAsyncSession(sessionInfo, deviceSessionContext); - transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), TransportServiceCallback.EMPTY); + transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), TransportServiceCallback.EMPTY); + deviceSessionContext.setSessionInfo(sessionInfo); deviceSessionContext.setDeviceInfo(msg.getDeviceInfo()); } else { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java new file mode 100644 index 0000000000..a3b5f77dfb --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java @@ -0,0 +1,150 @@ +/** + * Copyright © 2016-2021 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.snmp.service; + +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.snmp4j.PDU; +import org.snmp4j.ScopedPDU; +import org.snmp4j.smi.Integer32; +import org.snmp4j.smi.Null; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.Variable; +import org.snmp4j.smi.VariableBinding; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@TbSnmpTransportComponent +@Service +@Slf4j +public class PduMapper { + public PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + PDU pdu; + SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); + SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); + switch (snmpVersion) { + case V1: + case V2C: + pdu = new PDU(); + break; + case V3: + ScopedPDU scopedPdu = new ScopedPDU(); + scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); + scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); + pdu = scopedPdu; + break; + default: + throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); + } + + pdu.setType(communicationConfig.getMethod().getCode()); + pdu.addAll(communicationConfig.getAllMappings().stream() + .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) + .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) + .map(value -> { + Variable variable = toSnmpVariable(mapping, value); + return new VariableBinding(new OID(mapping.getOid()), variable); + }) + .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) + .collect(Collectors.toList())); + + return pdu; + } + + private Variable toSnmpVariable(SnmpMapping mapping, String value) { + Variable variable; + switch (mapping.getDataType()) { + case LONG: + try { + variable = new Integer32(Integer.parseInt(value)); + break; + } catch (NumberFormatException ignored) { + } + case DOUBLE: + case BOOLEAN: + case STRING: + case JSON: + default: + variable = new OctetString(value); + } + return variable; + } + + + public JsonObject processPdu(PDU pdu, DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { + List variablesBindings = IntStream.range(0, pdu.size()) + .mapToObj(pdu::get) + .filter(Objects::nonNull) + .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) + .collect(Collectors.toList()); + JsonObject data = new JsonObject(); + + Map mappings = new HashMap<>(); + for (SnmpMapping mapping : communicationConfig.getAllMappings()) { + OID oid = new OID(mapping.getOid()); + mappings.put(oid, mapping); + } + + variablesBindings.forEach(variableBinding -> { + log.trace("Processing variable binding: {}", variableBinding); + + OID oid = variableBinding.getOid(); + SnmpMapping mapping = mappings.get(oid); + if (mapping == null) { + log.debug("No SNMP mapping for oid {}", oid); + return; + } + + processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), data); + }); + + return data; + } + + + private void processValue(String key, DataType dataType, String value, JsonObject result) { + switch (dataType) { + case LONG: + result.addProperty(key, Long.parseLong(value)); + break; + case BOOLEAN: + result.addProperty(key, Boolean.parseBoolean(value)); + break; + case DOUBLE: + result.addProperty(key, Double.parseDouble(value)); + break; + case STRING: + case JSON: + default: + result.addProperty(key, value); + } + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpAuthService.java similarity index 94% rename from common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java rename to common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpAuthService.java index 81187fb33e..c3ed188bb0 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpAuthService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.snmp; +package org.thingsboard.server.transport.snmp.service; import lombok.RequiredArgsConstructor; import org.snmp4j.AbstractTarget; @@ -24,6 +24,7 @@ import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; +import org.snmp4j.smi.Address; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; @@ -36,6 +37,8 @@ import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.service.SnmpTransportService; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; +import java.util.Optional; + @Service @TbSnmpTransportComponent @RequiredArgsConstructor @@ -97,7 +100,8 @@ public class SnmpAuthService { throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); } - target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getHost() + "/" + deviceTransportConfig.getPort())); + Address address = GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getHost() + "/" + deviceTransportConfig.getPort()); + target.setAddress(Optional.ofNullable(address).orElseThrow(() -> new IllegalArgumentException("Address of the SNMP device is invalid"))); target.setTimeout(profileTransportConfig.getTimeoutMs()); target.setRetries(profileTransportConfig.getRetries()); target.setVersion(protocolVersion.getCode()); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 94a18f3d54..f672e36190 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -16,11 +16,11 @@ package org.thingsboard.server.transport.snmp.service; import com.google.gson.JsonObject; +import lombok.Data; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.snmp4j.PDU; -import org.snmp4j.ScopedPDU; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; import org.snmp4j.event.ResponseEvent; @@ -28,26 +28,18 @@ import org.snmp4j.mp.MPv3; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; -import org.snmp4j.smi.Integer32; -import org.snmp4j.smi.Null; -import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; -import org.snmp4j.smi.Variable; -import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.TbTransportService; -import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; -import org.thingsboard.server.common.data.transport.snmp.configs.RepeatingQueryingSnmpCommunicationConfig; -import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcResponseQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -60,44 +52,37 @@ import javax.annotation.PreDestroy; import java.io.IOException; import java.util.Collections; import java.util.EnumMap; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; import java.util.stream.Collectors; @TbSnmpTransportComponent @Service @Slf4j +@RequiredArgsConstructor public class SnmpTransportService implements TbTransportService { private final TransportService transportService; + private final PduMapper pduMapper; @Getter private Snmp snmp; private ScheduledExecutorService queryingExecutor; private ExecutorService responseProcessingExecutor; - private final Map> responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); + private final Map responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); @Value("${transport.snmp.response_processing.parallelism_level}") private Integer responseProcessingParallelismLevel; @Value("${transport.snmp.underlying_protocol}") private String snmpUnderlyingProtocol; - public SnmpTransportService(TransportService transportService) { - this.transportService = transportService; - } - @PostConstruct private void init() throws IOException { - log.info("Initializing SNMP transport service"); - queryingExecutor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("snmp-querying")); responseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); @@ -128,26 +113,23 @@ public class SnmpTransportService implements TbTransportService { public void createQueryingTasks(DeviceSessionContext sessionContext) { List> queryingTasks = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() - .filter(config -> config.getSpec().isRepeatingQuerying()) + .filter(communicationConfig -> communicationConfig instanceof RepeatingQueryingSnmpCommunicationConfig) .map(config -> { RepeatingQueryingSnmpCommunicationConfig repeatingCommunicationConfig = (RepeatingQueryingSnmpCommunicationConfig) config; - return createQueryingTaskForConfig(sessionContext, repeatingCommunicationConfig); + Long queryingFrequency = repeatingCommunicationConfig.getQueryingFrequencyMs(); + + return queryingExecutor.scheduleWithFixedDelay(() -> { + try { + if (sessionContext.isActive()) { + sendRequest(sessionContext, repeatingCommunicationConfig); + } + } catch (Exception e) { + log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); + } + }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); }) .collect(Collectors.toList()); - sessionContext.setQueryingTasks(queryingTasks); - } - - private ScheduledFuture createQueryingTaskForConfig(DeviceSessionContext sessionContext, RepeatingQueryingSnmpCommunicationConfig communicationConfig) { - Long queryingFrequency = communicationConfig.getQueryingFrequencyMs(); - return queryingExecutor.scheduleWithFixedDelay(() -> { - try { - if (sessionContext.isActive()) { - sendRequest(sessionContext, communicationConfig); - } - } catch (Exception e) { - log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); - } - }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); + sessionContext.getQueryingTasks().addAll(queryingTasks); } public void cancelQueryingTasks(DeviceSessionContext sessionContext) { @@ -155,67 +137,23 @@ public class SnmpTransportService implements TbTransportService { sessionContext.getQueryingTasks().clear(); } - public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) throws IOException { + + public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { sendRequest(sessionContext, communicationConfig, Collections.emptyMap()); } - public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) throws IOException { - PDU request = createPdu(sessionContext, communicationConfig, values); - executeRequest(sessionContext, request); - } + public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + PDU request = pduMapper.createPdu(sessionContext, communicationConfig, values); - private void executeRequest(DeviceSessionContext sessionContext, PDU request) throws IOException { if (request.size() > 0) { log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings()); - snmp.send(request, sessionContext.getTarget(), sessionContext.getDeviceProfile().getId(), sessionContext); - } - } - - private PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { - PDU pdu; - SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); - SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); - switch (snmpVersion) { - case V1: - case V2C: - pdu = new PDU(); - break; - case V3: - ScopedPDU scopedPdu = new ScopedPDU(); - scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); - scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); - pdu = scopedPdu; - break; - default: - throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); + RequestInfo requestInfo = new RequestInfo(sessionContext.getDeviceProfile().getId(), communicationConfig); + try { + snmp.send(request, sessionContext.getTarget(), requestInfo, sessionContext); + } catch (IOException e) { + log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString()); + } } - - pdu.setType(communicationConfig.getMethod().getCode()); - pdu.addAll(communicationConfig.getMappings().stream() - .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) - .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) - .map(value -> { - Variable variable; - switch (mapping.getDataType()) { - case LONG: - try { - variable = new Integer32(Integer.parseInt(value)); - break; - } catch (NumberFormatException ignored) { - } - case DOUBLE: - case BOOLEAN: - case STRING: - case JSON: - default: - variable = new OctetString(value); - } - return new VariableBinding(new OID(mapping.getOid()), variable); - }) - .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) - .collect(Collectors.toList())); - - return pdu; } @@ -223,7 +161,7 @@ public class SnmpTransportService implements TbTransportService { ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); if (event.getError() != null) { - log.warn("Response error: {}", event.getError().getMessage(), event.getError()); + log.warn("SNMP response error: {}", event.getError().toString()); return; } @@ -232,108 +170,65 @@ public class SnmpTransportService implements TbTransportService { log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); return; } - DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); - log.debug("[{}] Processing SNMP response for device {} with device profile {}: {}", - response.getRequestID(), sessionContext.getDeviceId(), deviceProfileId, response); - responseProcessingExecutor.execute(() -> processResponse(sessionContext, response)); + RequestInfo requestInfo = (RequestInfo) event.getUserObject(); + responseProcessingExecutor.execute(() -> { + processResponse(sessionContext, response, requestInfo); + }); } - private void processResponse(DeviceSessionContext sessionContext, PDU responsePdu) { - Map mappings = new HashMap<>(); - Map configs = new HashMap<>(); - Map responses = new EnumMap<>(SnmpCommunicationSpec.class); - - for (SnmpCommunicationConfig config : sessionContext.getProfileTransportConfiguration().getCommunicationConfigs()) { - for (SnmpMapping mapping : config.getMappings()) { - OID oid = new OID(mapping.getOid()); - mappings.put(oid, mapping); - configs.put(oid, config); - } - responses.put(config.getSpec(), new JsonObject()); - } - - for (int i = 0; i < responsePdu.size(); i++) { - VariableBinding variableBinding = responsePdu.get(i); - log.trace("Processing variable binding {}: {}", i, variableBinding); - - if (variableBinding.getVariable() instanceof Null) { - log.debug("Response variable is empty"); - continue; - } - - OID oid = variableBinding.getOid(); - if (!mappings.containsKey(oid)) { - log.debug("No SNMP mapping for oid {}", oid); - continue; - } - - SnmpCommunicationSpec spec = configs.get(oid).getSpec(); - if (!responseProcessors.containsKey(spec)) { - log.debug("No response processor found for spec {}", spec); - continue; - } - - SnmpMapping mapping = mappings.get(oid); - processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), responses.get(spec)); + private void processResponse(DeviceSessionContext sessionContext, PDU response, RequestInfo requestInfo) { + ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationConfig().getSpec()); + if (responseProcessor == null) { + return; } - if (responses.values().stream().allMatch(response -> response.entrySet().isEmpty())) { - log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), responsePdu.getRequestID()); + JsonObject responseData = pduMapper.processPdu(response, sessionContext, requestInfo.getCommunicationConfig()); + if (responseData.entrySet().isEmpty()) { + log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), response.getRequestID()); return; } - responses.forEach((spec, response) -> { - Optional.ofNullable(responseProcessors.get(spec)) - .ifPresent(responseProcessor -> { - if (!response.entrySet().isEmpty()) { - responseProcessor.accept(response, sessionContext); - } - }); - }); - + responseProcessor.process(responseData, sessionContext); reportActivity(sessionContext.getSessionInfo()); } private void configureResponseProcessors() { responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (response, sessionContext) -> { TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); - transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); - log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), response); + transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null); + log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), response); }); responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (response, sessionContext) -> { TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); - transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); - log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), response); + transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null); + log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), response); + }); + + responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_RESPONSE_QUERYING, (response, sessionContext) -> { + String rpcResponse = response.get(ToDeviceRpcResponseQueryingSnmpCommunicationConfig.RPC_RESPONSE_KEY_NAME).getAsString(); + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() + .setPayload(rpcResponse) + .build(); + transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null); + log.debug("Processed RPC response from device {}: {}", sessionContext.getDeviceId(), rpcResponse); }); + +// responseProcessors.put(, (response, sessionContext) -> { +// TransportProtos.ClaimDeviceMsg claimDeviceMsg = JsonConverter.convertToClaimDeviceProto(sessionContext.getDeviceId(), response); +// transportService.process(sessionContext.getSessionInfo(), claimDeviceMsg, null); +// }); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(false) - .setRpcSubscription(false) + .setAttributeSubscription(true) + .setRpcSubscription(true) .setLastActivityTime(System.currentTimeMillis()) .build(), TransportServiceCallback.EMPTY); } - private void processValue(String key, DataType dataType, String value, JsonObject result) { - if (StringUtils.isEmpty(value)) return; - - switch (dataType) { - case LONG: - result.addProperty(key, Long.parseLong(value)); - break; - case BOOLEAN: - result.addProperty(key, Boolean.parseBoolean(value)); - break; - case DOUBLE: - result.addProperty(key, Double.parseDouble(value)); - break; - default: - result.addProperty(key, value); - } - } @Override public String getName() { @@ -358,4 +253,15 @@ public class SnmpTransportService implements TbTransportService { } log.info("SNMP transport stopped!"); } + + @Data + private static class RequestInfo { + private final DeviceProfileId deviceProfileId; + private final SnmpCommunicationConfig communicationConfig; + } + + private interface ResponseProcessor { + void process(JsonObject responseData, DeviceSessionContext sessionContext); + } + } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index b3a38545a8..82fdc037ad 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -27,6 +27,8 @@ import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfigu import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcCommandSettingSnmpCommunicationConfig; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; @@ -36,13 +38,13 @@ 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 org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; -import org.thingsboard.server.transport.snmp.SnmpAuthService; import org.thingsboard.server.transport.snmp.SnmpTransportContext; -import org.thingsboard.server.transport.snmp.service.SnmpTransportService; +import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; @@ -64,16 +66,12 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final SnmpTransportContext snmpTransportContext; - @Getter - @Setter - private long previousRequestExecutedAt = 0; private final AtomicInteger msgIdSeq = new AtomicInteger(0); @Getter private boolean isActive = true; @Getter - @Setter - private List> queryingTasks = new LinkedList<>(); + private final List> queryingTasks = new LinkedList<>(); public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, @@ -116,7 +114,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) throws Exception { log.trace("Initializing target for SNMP session of device {}", device); this.target = snmpTransportContext.getSnmpAuthService().setUpSnmpTarget(profileTransportConfig, deviceTransportConfig); - log.info("SNMP target initialized: {}", target); + log.debug("SNMP target initialized: {}", target); } public void close() { @@ -138,20 +136,14 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { - profileTransportConfiguration.getCommunicationConfigs().stream() - .filter(config -> config.getSpec() == SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) - .findFirst() + getCommunicationConfigForSpec(SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) .ifPresent(communicationConfig -> { Map sharedAttributes = JsonConverter.toJson(attributeUpdateNotification).entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() )); - try { - snmpTransportContext.getSnmpTransportService().sendRequest(this, communicationConfig, sharedAttributes); - } catch (Exception e) { - log.error("Failed to send request with shared attributes to SNMP device {}: {}", getDeviceId(), e.getMessage()); - } + snmpTransportContext.getSnmpTransportService().sendRequest(this, communicationConfig, sharedAttributes); }); } @@ -161,9 +153,23 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { + getCommunicationConfigForSpec(SnmpCommunicationSpec.TO_DEVICE_RPC_COMMAND_SETTING) + .ifPresent(communicationConfig -> { + String value = JsonConverter.toJson(toDeviceRequest, true).toString(); + snmpTransportContext.getSnmpTransportService().sendRequest( + this, communicationConfig, + Map.of(ToDeviceRpcCommandSettingSnmpCommunicationConfig.RPC_COMMAND_KEY_NAME, value) + ); + }); } @Override public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { } + + private Optional getCommunicationConfigForSpec(SnmpCommunicationSpec spec) { + return profileTransportConfiguration.getCommunicationConfigs().stream() + .filter(config -> config.getSpec() == spec) + .findFirst(); + } } From a1e4bbbb31cca6360f77dd838d83872e646ed917 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 20 Apr 2021 17:58:15 +0300 Subject: [PATCH 043/127] Implement RPC for SNMP; refactor --- .../SnmpDeviceTransportConfiguration.java | 2 +- ...mpDeviceProfileTransportConfiguration.java | 2 +- .../transport/snmp/SnmpCommunicationSpec.java | 3 +- ...CommandSettingSnmpCommunicationConfig.java | 59 -------- ...sponseQueryingSnmpCommunicationConfig.java | 60 -------- .../transport/snmp/SnmpTransportContext.java | 1 + .../{PduMapper.java => PduService.java} | 91 +++++++----- .../snmp/service/SnmpTransportService.java | 135 ++++++++++++++---- .../snmp/session/DeviceSessionContext.java | 33 +---- .../transport/adaptor/JsonConverter.java | 11 +- 10 files changed, 175 insertions(+), 222 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java rename common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/{PduMapper.java => PduService.java} (76%) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index 68a9d1f218..a7bc143d81 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -40,7 +40,7 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur private String community; /* - * For SNMP v3 with User Based Security Model + * For SNMP v3 * */ private String username; private String securityName; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index bbf074eeda..0b8efaf8c7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -44,7 +44,7 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra @JsonIgnore private boolean isValid() { return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 - && communicationConfigs != null && !communicationConfigs.isEmpty() + && communicationConfigs != null && communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) && communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid) .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java index a6643ecf1e..8d87144ae8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java @@ -21,6 +21,5 @@ public enum SnmpCommunicationSpec { CLIENT_ATTRIBUTES_QUERYING, SHARED_ATTRIBUTES_SETTING, - TO_DEVICE_RPC_COMMAND_SETTING, - TO_DEVICE_RPC_RESPONSE_QUERYING + TO_DEVICE_RPC_REQUEST, } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java deleted file mode 100644 index 97f3de047b..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright © 2016-2021 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.transport.snmp.config.impl; - -import lombok.Data; -import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; -import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -@Data -public class ToDeviceRpcCommandSettingSnmpCommunicationConfig implements SnmpCommunicationConfig { - private SnmpMapping mapping; - - @Override - public SnmpCommunicationSpec getSpec() { - return SnmpCommunicationSpec.TO_DEVICE_RPC_COMMAND_SETTING; - } - - @Override - public SnmpMethod getMethod() { - return SnmpMethod.SET; - } - - public void setMapping(SnmpMapping mapping) { - this.mapping = mapping != null ? new SnmpMapping(mapping.getOid(), RPC_COMMAND_KEY_NAME, DataType.STRING) : null; - } - - @Override - public List getAllMappings() { - return Collections.singletonList(mapping); - } - - @Override - public boolean isValid() { - return mapping != null && mapping.isValid(); - } - - public static final String RPC_COMMAND_KEY_NAME = "rpcCommand"; - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java deleted file mode 100644 index 18bce94980..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright © 2016-2021 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.transport.snmp.config.impl; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; -import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; - -import java.util.Collections; -import java.util.List; - -@EqualsAndHashCode(callSuper = true) -@Data -public class ToDeviceRpcResponseQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { - private SnmpMapping mapping; - - @Override - public SnmpCommunicationSpec getSpec() { - return SnmpCommunicationSpec.TO_DEVICE_RPC_RESPONSE_QUERYING; - } - - @Override - public SnmpMethod getMethod() { - return SnmpMethod.GET; - } - - public void setMapping(SnmpMapping mapping) { - this.mapping = mapping != null ? new SnmpMapping(mapping.getOid(), RPC_RESPONSE_KEY_NAME, DataType.STRING) : null; - } - - @Override - public List getAllMappings() { - return Collections.singletonList(mapping); - } - - @Override - public boolean isValid() { - return true; - } - - public static final String RPC_RESPONSE_KEY_NAME = "rpcResponse"; - -} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index d66d09ab0b..68d4dd3933 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -152,6 +152,7 @@ public class SnmpTransportContext extends TransportContext { } } catch (Exception e) { log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage()); + destroyDeviceSession(sessionContext); } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java similarity index 76% rename from common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java rename to common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java index a3b5f77dfb..720b1e7ae3 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java @@ -29,6 +29,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; @@ -45,32 +46,16 @@ import java.util.stream.IntStream; @TbSnmpTransportComponent @Service @Slf4j -public class PduMapper { +public class PduService { public PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { - PDU pdu; - SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); - SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); - switch (snmpVersion) { - case V1: - case V2C: - pdu = new PDU(); - break; - case V3: - ScopedPDU scopedPdu = new ScopedPDU(); - scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); - scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); - pdu = scopedPdu; - break; - default: - throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); - } + PDU pdu = setUpPdu(sessionContext); pdu.setType(communicationConfig.getMethod().getCode()); pdu.addAll(communicationConfig.getAllMappings().stream() .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) .map(value -> { - Variable variable = toSnmpVariable(mapping, value); + Variable variable = toSnmpVariable(value, mapping.getDataType()); return new VariableBinding(new OID(mapping.getOid()), variable); }) .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) @@ -79,9 +64,20 @@ public class PduMapper { return pdu; } - private Variable toSnmpVariable(SnmpMapping mapping, String value) { + public PDU createSingleVariablePdu(DeviceSessionContext sessionContext, SnmpMethod snmpMethod, String oid, String value, DataType dataType) { + PDU pdu = setUpPdu(sessionContext); + pdu.setType(snmpMethod.getCode()); + + Variable variable = value == null ? Null.instance : toSnmpVariable(value, dataType); + pdu.add(new VariableBinding(new OID(oid), variable)); + + return pdu; + } + + private Variable toSnmpVariable(String value, DataType dataType) { + dataType = dataType == null ? DataType.STRING : dataType; Variable variable; - switch (mapping.getDataType()) { + switch (dataType) { case LONG: try { variable = new Integer32(Integer.parseInt(value)); @@ -98,37 +94,62 @@ public class PduMapper { return variable; } + private PDU setUpPdu(DeviceSessionContext sessionContext) { + PDU pdu; + SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); + SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); + switch (snmpVersion) { + case V1: + case V2C: + pdu = new PDU(); + break; + case V3: + ScopedPDU scopedPdu = new ScopedPDU(); + scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); + scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); + pdu = scopedPdu; + break; + default: + throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); + } + return pdu; + } - public JsonObject processPdu(PDU pdu, DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { - List variablesBindings = IntStream.range(0, pdu.size()) - .mapToObj(pdu::get) - .filter(Objects::nonNull) - .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) - .collect(Collectors.toList()); - JsonObject data = new JsonObject(); + + public JsonObject processPdu(PDU pdu, List responseMappings) { + Map values = processPdu(pdu); Map mappings = new HashMap<>(); - for (SnmpMapping mapping : communicationConfig.getAllMappings()) { - OID oid = new OID(mapping.getOid()); - mappings.put(oid, mapping); + if (responseMappings != null) { + for (SnmpMapping mapping : responseMappings) { + OID oid = new OID(mapping.getOid()); + mappings.put(oid, mapping); + } } - variablesBindings.forEach(variableBinding -> { - log.trace("Processing variable binding: {}", variableBinding); + JsonObject data = new JsonObject(); + values.forEach((oid, value) -> { + log.trace("Processing variable binding: {} - {}", oid, value); - OID oid = variableBinding.getOid(); SnmpMapping mapping = mappings.get(oid); if (mapping == null) { log.debug("No SNMP mapping for oid {}", oid); return; } - processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), data); + processValue(mapping.getKey(), mapping.getDataType(), value, data); }); return data; } + public Map processPdu(PDU pdu) { + return IntStream.range(0, pdu.size()) + .mapToObj(pdu::get) + .filter(Objects::nonNull) + .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) + .collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString)); + } private void processValue(String key, DataType dataType, String value, JsonObject result) { switch (dataType) { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index f672e36190..5bfee14c41 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.snmp.service; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import lombok.Data; import lombok.Getter; @@ -35,11 +36,12 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.TbTransportService; -import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; -import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcResponseQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -50,10 +52,12 @@ import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -67,13 +71,14 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class SnmpTransportService implements TbTransportService { private final TransportService transportService; - private final PduMapper pduMapper; + private final PduService pduService; @Getter private Snmp snmp; private ScheduledExecutorService queryingExecutor; private ExecutorService responseProcessingExecutor; + private final Map responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class); private final Map responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); @Value("${transport.snmp.response_processing.parallelism_level}") @@ -87,6 +92,7 @@ public class SnmpTransportService implements TbTransportService { responseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); initializeSnmp(); + configureResponseDataMappers(); configureResponseProcessors(); log.info("SNMP transport service initialized"); @@ -138,16 +144,19 @@ public class SnmpTransportService implements TbTransportService { } - public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { + private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { sendRequest(sessionContext, communicationConfig, Collections.emptyMap()); } - public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { - PDU request = pduMapper.createPdu(sessionContext, communicationConfig, values); + private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + PDU request = pduService.createPdu(sessionContext, communicationConfig, values); + RequestInfo requestInfo = new RequestInfo(communicationConfig.getSpec(), communicationConfig.getAllMappings()); + sendRequest(sessionContext, request, requestInfo); + } + private void sendRequest(DeviceSessionContext sessionContext, PDU request, RequestInfo requestInfo) { if (request.size() > 0) { log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings()); - RequestInfo requestInfo = new RequestInfo(sessionContext.getDeviceProfile().getId(), communicationConfig); try { snmp.send(request, sessionContext.getTarget(), requestInfo, sessionContext); } catch (IOException e) { @@ -156,6 +165,39 @@ public class SnmpTransportService implements TbTransportService { } } + public void onAttributeUpdate(DeviceSessionContext sessionContext, TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification) { + sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() + .filter(config -> config.getSpec() == SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) + .findFirst() + .ifPresent(communicationConfig -> { + Map sharedAttributes = JsonConverter.toJson(attributeUpdateNotification).entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() + )); + sendRequest(sessionContext, communicationConfig, sharedAttributes); + }); + } + + public void onToDeviceRpcRequest(DeviceSessionContext sessionContext, TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) { + SnmpMethod snmpMethod = SnmpMethod.valueOf(toDeviceRpcRequestMsg.getMethodName()); + JsonObject params = JsonConverter.parse(toDeviceRpcRequestMsg.getParams()).getAsJsonObject(); + + String oid = Optional.ofNullable(params.get("oid")).map(JsonElement::getAsString).orElse(null); + String value = Optional.ofNullable(params.get("value")).map(JsonElement::getAsString).orElse(null); + DataType dataType = Optional.ofNullable(params.get("dataType")).map(e -> DataType.valueOf(e.getAsString())).orElse(DataType.STRING); + + if (oid == null || oid.isEmpty()) { + throw new IllegalArgumentException("OID in to-device RPC request is not specified"); + } + if (value == null && snmpMethod == SnmpMethod.SET) { + throw new IllegalArgumentException("Value must be specified for SNMP method 'SET'"); + } + + PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType); + sendRequest(sessionContext, request, new RequestInfo(toDeviceRpcRequestMsg.getRequestId(), SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST)); + } + public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) { ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); @@ -178,47 +220,59 @@ public class SnmpTransportService implements TbTransportService { } private void processResponse(DeviceSessionContext sessionContext, PDU response, RequestInfo requestInfo) { - ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationConfig().getSpec()); - if (responseProcessor == null) { - return; - } + ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationSpec()); + if (responseProcessor == null) return; + + JsonObject responseData = responseDataMappers.get(requestInfo.getCommunicationSpec()).map(response, requestInfo); - JsonObject responseData = pduMapper.processPdu(response, sessionContext, requestInfo.getCommunicationConfig()); if (responseData.entrySet().isEmpty()) { log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), response.getRequestID()); return; } - responseProcessor.process(responseData, sessionContext); + responseProcessor.process(responseData, requestInfo, sessionContext); reportActivity(sessionContext.getSessionInfo()); } + private void configureResponseDataMappers() { + responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdu, requestInfo) -> { + JsonObject responseData = new JsonObject(); + pduService.processPdu(pdu).forEach((oid, value) -> { + responseData.addProperty(oid.toDottedString(), value); + }); + return responseData; + }); + + ResponseDataMapper defaultResponseDataMapper = (pdu, requestInfo) -> { + return pduService.processPdu(pdu, requestInfo.getResponseMappings()); + }; + Arrays.stream(SnmpCommunicationSpec.values()) + .forEach(communicationSpec -> { + responseDataMappers.putIfAbsent(communicationSpec, defaultResponseDataMapper); + }); + } + private void configureResponseProcessors() { - responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (response, sessionContext) -> { - TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); + responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestInfo, sessionContext) -> { + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(responseData); transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null); - log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), response); + log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); }); - responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (response, sessionContext) -> { - TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); + responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestInfo, sessionContext) -> { + TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(responseData); transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null); - log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), response); + log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); }); - responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_RESPONSE_QUERYING, (response, sessionContext) -> { - String rpcResponse = response.get(ToDeviceRpcResponseQueryingSnmpCommunicationConfig.RPC_RESPONSE_KEY_NAME).getAsString(); + responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestInfo, sessionContext) -> { TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() - .setPayload(rpcResponse) + .setRequestId(requestInfo.getRequestId()) + .setPayload(JsonConverter.toJson(responseData)) .build(); transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null); - log.debug("Processed RPC response from device {}: {}", sessionContext.getDeviceId(), rpcResponse); + log.debug("Posted RPC response {} for device {}", responseData, sessionContext.getDeviceId()); }); - -// responseProcessors.put(, (response, sessionContext) -> { -// TransportProtos.ClaimDeviceMsg claimDeviceMsg = JsonConverter.convertToClaimDeviceProto(sessionContext.getDeviceId(), response); -// transportService.process(sessionContext.getSessionInfo(), claimDeviceMsg, null); -// }); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { @@ -256,12 +310,31 @@ public class SnmpTransportService implements TbTransportService { @Data private static class RequestInfo { - private final DeviceProfileId deviceProfileId; - private final SnmpCommunicationConfig communicationConfig; + private Integer requestId; + private SnmpCommunicationSpec communicationSpec; + private List responseMappings; + + public RequestInfo(Integer requestId, SnmpCommunicationSpec communicationSpec) { + this.requestId = requestId; + this.communicationSpec = communicationSpec; + } + + public RequestInfo(SnmpCommunicationSpec communicationSpec) { + this.communicationSpec = communicationSpec; + } + + public RequestInfo(SnmpCommunicationSpec communicationSpec, List responseMappings) { + this.communicationSpec = communicationSpec; + this.responseMappings = responseMappings; + } + } + + private interface ResponseDataMapper { + JsonObject map(PDU pdu, RequestInfo requestInfo); } private interface ResponseProcessor { - void process(JsonObject responseData, DeviceSessionContext sessionContext); + void process(JsonObject responseData, RequestInfo requestInfo, DeviceSessionContext sessionContext); } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index 82fdc037ad..a59ce020ba 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -26,11 +26,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; -import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcCommandSettingSnmpCommunicationConfig; import org.thingsboard.server.common.transport.SessionMsgListener; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -40,15 +36,11 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.transport.snmp.SnmpTransportContext; -import java.io.IOException; import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; @Slf4j public class DeviceSessionContext extends DeviceAwareSessionContext implements SessionMsgListener, ResponseListener { @@ -136,15 +128,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { - getCommunicationConfigForSpec(SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) - .ifPresent(communicationConfig -> { - Map sharedAttributes = JsonConverter.toJson(attributeUpdateNotification).entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() - )); - snmpTransportContext.getSnmpTransportService().sendRequest(this, communicationConfig, sharedAttributes); - }); + snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification); } @Override @@ -153,23 +137,10 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { - getCommunicationConfigForSpec(SnmpCommunicationSpec.TO_DEVICE_RPC_COMMAND_SETTING) - .ifPresent(communicationConfig -> { - String value = JsonConverter.toJson(toDeviceRequest, true).toString(); - snmpTransportContext.getSnmpTransportService().sendRequest( - this, communicationConfig, - Map.of(ToDeviceRpcCommandSettingSnmpCommunicationConfig.RPC_COMMAND_KEY_NAME, value) - ); - }); + snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); } @Override public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { } - - private Optional getCommunicationConfigForSpec(SnmpCommunicationSpec spec) { - return profileTransportConfiguration.getCommunicationConfigs().stream() - .filter(config -> config.getSpec() == spec) - .findFirst(); - } } 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 fe2f3500a6..fce1c2c9b5 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 @@ -558,6 +558,14 @@ public class JsonConverter { } } + public static JsonElement parse(String json) { + return JSON_PARSER.parse(json); + } + + public static String toJson(JsonElement element) { + return GSON.toJson(element); + } + public static void setTypeCastEnabled(boolean enabled) { isTypeCastEnabled = enabled; } @@ -599,8 +607,7 @@ public class JsonConverter { .build(); } - private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String - provisionKey, String provisionSecret) { + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) { return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder() .setProvisionDeviceKey(provisionKey) .setProvisionDeviceSecret(provisionSecret) From c7269528fafff6f78e612d1242ac76c27310997a Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard <77969531+AndrewVolosytnykhThingsboard@users.noreply.github.com> Date: Wed, 21 Apr 2021 15:05:45 +0300 Subject: [PATCH 044/127] [PROD-881] Dynamic alarm severity in Create Alarm Rule Node (#4393) * Dynamic alarm severity feature added * Tests for dynamic alarm severity added * Removed redundant imports * Join front-end part * Refactoring: create of method processAlarmSeverity, remove incorrect solution and process severity in correct places * Correct updating of alarm parsed from msg data * Removed redundant operations * Correct exceptions throwing --- .../rule/engine/action/TbCreateAlarmNode.java | 27 +++- .../TbCreateAlarmNodeConfiguration.java | 6 +- .../static/rulenode/rulenode-core-config.js | 2 +- .../rule/engine/action/TbAlarmNodeTest.java | 122 +++++++++++++++++- 4 files changed, 151 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java index c941d6ca92..3babc27917 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java @@ -22,12 +22,14 @@ 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.lang3.EnumUtils; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; 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.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentType; @@ -56,6 +58,19 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode relationTypes; + private AlarmSeverity notDynamicAlarmSeverity; + + @Override + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + super.init(ctx, configuration); + if(!this.config.isDynamicSeverity()) { + this.notDynamicAlarmSeverity = EnumUtils.getEnum(AlarmSeverity.class, this.config.getSeverity()); + if(this.notDynamicAlarmSeverity == null) { + throw new TbNodeException("Incorrect Alarm Severity value: " + this.config.getSeverity()); + } + } + } + @Override protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException { @@ -132,7 +147,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode { - private AlarmSeverity severity; + private String severity; private boolean propagate; private boolean useMessageAlarmData; + private boolean dynamicSeverity; private List relationTypes; @@ -40,10 +41,11 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura "}\n" + "return details;"); configuration.setAlarmType("General Alarm"); - configuration.setSeverity(AlarmSeverity.CRITICAL); + configuration.setSeverity(AlarmSeverity.CRITICAL.name()); configuration.setPropagate(false); configuration.setUseMessageAlarmData(false); configuration.setRelationTypes(Collections.emptyList()); + configuration.setDynamicSeverity(false); return configuration; } } 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 4eea9d1764..810ccb3c18 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 @@ -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 y=function(e,t){return(y=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 b(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}y(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function h(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 C(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function v(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 F,x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-node-empty-config",template:"
"}),C("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.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return b(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=h([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'}),C("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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),I=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.prepareOutputConfig=function(e){return e.customerNamePattern=e.customerNamePattern.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),N=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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 tb.rulenode.relation-types-list-hint\n \n
\n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],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.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return b(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,i.Validators.pattern(/.*\S.*/)]):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,i.Validators.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.prototype.prepareOutputConfig=function(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("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}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=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 b(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,i.Validators.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.prototype.prepareOutputConfig=function(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),P=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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'}),C("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"}(F||(F={}));var M,w=new Map([[F.CUSTOMER,"tb.rulenode.originator-customer"],[F.TENANT,"tb.rulenode.originator-tenant"],[F.RELATED,"tb.rulenode.originator-related"],[F.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,D=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,K=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 B,G=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"}(B||(B={}));var H,U,j,z=new Map([[B.TITLE,"tb.rulenode.entity-details-title"],[B.COUNTRY,"tb.rulenode.entity-details-country"],[B.STATE,"tb.rulenode.entity-details-state"],[B.ZIP,"tb.rulenode.entity-details-zip"],[B.ADDRESS,"tb.rulenode.entity-details-address"],[B.ADDRESS2,"tb.rulenode.entity-details-address2"],[B.PHONE,"tb.rulenode.entity-details-phone"],[B.EMAIL,"tb.rulenode.entity-details-email"],[B.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(H||(H={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(j||(j={}));var Q,_=new Map([[j.STANDARD,"tb.rulenode.sqs-queue-standard"],[j.FIFO,"tb.rulenode.sqs-queue-fifo"]]),$=["anonymous","basic","cert.PEM"],W=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),J=["sas","cert.PEM"],Y=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"}(Q||(Q={}));var Z=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],X=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"]]),ee=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=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(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=h([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'}),C("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 b(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=h([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'}),C("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 b(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=h([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.prototype.prepareOutputConfig=function(e){return e.tableName=e.tableName.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.custom-table-hint\n \n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ae=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 b(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=v(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",String)],r.prototype,"requiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyRequiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valRequiredText",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=h([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\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}"]}),C("design:paramtypes",[o.Store,n.TranslateService,t.Injector,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.entityType=a.EntityType,n.propagateChange=null,n}var n;return b(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([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}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=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 b(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([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}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),le=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=v(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 b(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}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",String)],r.prototype,"label",void 0),h([t.Input(),C("design:type",Object)],r.prototype,"placeholder",void 0),h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.ViewChild("chipList",{static:!1}),C("design:type",d.MatChipList)],r.prototype,"chipList",void 0),h([t.ViewChild("messageTypeAutocomplete",{static:!1}),C("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),h([t.ViewChild("messageTypeInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=h([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}]}),C("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.subscriptions=[],n.disableCertPemCredentials=!1,n.allCredentialsTypes=$,n.credentialsTypeTranslationsMap=W,n.propagateChange=null,n}var n;return b(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.credentialsConfigFormGroup=this.fb.group({type:[null,[i.Validators.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(f.distinctUntilChanged()).subscribe((function(){e.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((function(){e.credentialsTypeChanged()})))},r.prototype.ngOnChanges=function(e){var t,r,n=this;try{for(var a=v(Object.keys(e)),o=a.next();!o.done;o=a.next()){var i=o.value,l=e[i];if(!l.firstChange&&l.currentValue!==l.previousValue)if(l.currentValue&&"disableCertPemCredentials"===i)"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((function(){n.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}},r.prototype.ngOnDestroy=function(){this.subscriptions.forEach((function(e){return e.unsubscribe()}))},r.prototype.writeValue=function(e){s.isDefinedAndNotNull(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))},r.prototype.setDisabledState=function(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())},r.prototype.updateView=function(){var e=this.credentialsConfigFormGroup.value,t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.validate=function(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}},r.prototype.credentialsTypeChanged=function(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()},r.prototype.updateValidators=function(e){void 0===e&&(e=!1);var t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([i.Validators.required]),this.credentialsConfigFormGroup.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(i.Validators.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})},r.prototype.requiredFilesSelected=function(e,t){return void 0===t&&(t=null),function(r){return t||(t=[Object.keys(r.controls)]),(null==r?void 0:r.controls)&&t.some((function(t){return t.every((function(t){return!e(r.controls[t])}))}))?null:{notAllRequiredFilesSelected:!0}}},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",Object)],r.prototype,"disableCertPemCredentials",void 0),r=n=h([t.Component({selector:"tb-credentials-config",template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.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
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n
\n
\n
\n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),me=function(){function e(e){this.sanitizer=e}return e.prototype.transform=function(e){return this.sanitizer.bypassSecurityTrustHtml(e)},e.ctorParameters=function(){return[{type:g.DomSanitizer}]},e=h([t.Pipe({name:"safeHtml"}),C("design:paramtypes",[g.DomSanitizer])],e)}(),ue=function(){function e(){}return e=h([t.NgModule({declarations:[ae,oe,ie,le,se,me],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ae,oe,ie,le,se,me]})],e)}(),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.prepareOutputConfig=function(e){return e.customerNamePattern=e.customerNamePattern.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("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 b(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=h([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'}),C("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.sqsQueueType=j,n.sqsQueueTypes=Object.keys(j),n.sqsQueueTypeTranslationsMap=_,n}return b(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=h([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'}),C("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}return b(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=h([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'}),C("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.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Z,n.ToByteStandartCharsetTypeTranslationMap=X,n}return b(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=h([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 \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'}),C("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}return b(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:[e?e.credentials:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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'}),C("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.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return b(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=h([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'}),C("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.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(Q),n}return b(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,[]],credentials:[e?e.credentials: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}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\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
\n'}),C("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.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return b(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=h([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'}),C("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.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return b(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=h([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),C("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.allAzureIotHubCredentialsTypes=J,n.azureIotHubCredentialsTypeTranslationsMap=Y,n}return b(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=h([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}"]}),C("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}return b(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=h([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'}),C("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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(){function e(){}return e=h([t.NgModule({declarations:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,ue],exports:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te]})],e)}(),Se=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 b(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=h([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}"]}),C("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.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=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=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),C("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.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 b(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=h([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}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ae=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Le=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=v(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 b(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}]},h([t.ViewChild("alarmStatusInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=h([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'}),C("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(){function e(){}return e=h([t.NgModule({declarations:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le],imports:[r.CommonModule,a.SharedModule,ue],exports:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le]})],e)}(),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=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=z,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=v(Object.keys(B)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(B[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 b(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(z.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(z.get(B[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}]},h([t.ViewChild("detailsInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=h([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}"]}),C("design:paramtypes",[o.Store,n.TranslateService,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=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(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=h([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
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("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.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(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=h([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
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("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 b(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=h([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=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=H,n.fetchModes=Object.keys(H),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(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===H.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=h([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}"]}),C("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}return b(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=h([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'}),C("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}return b(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=h([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("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.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(r,e),r.prototype.configForm=function(){return this.calculateDeltaConfigForm},r.prototype.onConfigurationSet=function(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e?e.inputValueKey:null,[i.Validators.required]],outputValueKey:[e?e.outputValueKey:null,[i.Validators.required]],useCache:[e?e.useCache:null,[]],addPeriodBetweenMsgs:[!!e&&e.addPeriodBetweenMsgs,[]],periodValueKey:[e?e.periodValueKey:null,[]],round:[e?e.round:null,[i.Validators.min(0),i.Validators.max(15)]],tellFailureIfDeltaIsNegative:[e?e.tellFailureIfDeltaIsNegative:null,[]]})},r.prototype.updateValidators=function(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([i.Validators.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})},r.prototype.validatorTriggers=function(){return["addPeriodBetweenMsgs"]},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-calculate-delta-config",template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(){function e(){}return e=h([t.NgModule({declarations:[Me,we,Re,De,Oe,Ke,Be,Ge,He],imports:[r.CommonModule,a.SharedModule,ue],exports:[Me,we,Re,De,Oe,Ke,Be,Ge,He]})],e)}(),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=F,n.originatorSources=Object.keys(F),n.originatorSourceTranslationMap=w,n}return b(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===F.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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ze=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),_e=function(){function e(){}return e=h([t.NgModule({declarations:[je,ze,Qe],imports:[r.CommonModule,a.SharedModule,ue],exports:[je,ze,Qe]})],e)}(),$e=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-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","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-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","shared-attributes":"Shared attributes","server-attributes":"Server attributes","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","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-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 period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","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","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',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",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","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"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-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","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":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client 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 interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data 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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","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","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body'},"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=h([t.NgModule({declarations:[x],imports:[r.CommonModule,a.SharedModule],exports:[qe,Pe,Ue,_e,x]}),C("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=$e,e.ɵa=x,e.ɵb=qe,e.ɵba=ve,e.ɵbb=Fe,e.ɵbc=xe,e.ɵbd=Te,e.ɵbe=ue,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=le,e.ɵbj=se,e.ɵbk=me,e.ɵbl=Pe,e.ɵbm=Se,e.ɵbn=Ie,e.ɵbo=ke,e.ɵbp=Ne,e.ɵbq=Ve,e.ɵbr=Ee,e.ɵbs=Ae,e.ɵbt=Le,e.ɵbu=Ue,e.ɵbv=Me,e.ɵbw=we,e.ɵbx=Re,e.ɵby=De,e.ɵbz=Oe,e.ɵc=T,e.ɵca=Ke,e.ɵcb=Be,e.ɵcc=Ge,e.ɵcd=He,e.ɵce=_e,e.ɵcf=je,e.ɵcg=ze,e.ɵch=Qe,e.ɵd=q,e.ɵe=S,e.ɵf=I,e.ɵg=k,e.ɵh=N,e.ɵi=V,e.ɵj=E,e.ɵk=A,e.ɵl=L,e.ɵm=P,e.ɵn=ee,e.ɵo=te,e.ɵp=re,e.ɵq=ne,e.ɵr=de,e.ɵs=pe,e.ɵt=ce,e.ɵu=fe,e.ɵv=ge,e.ɵw=ye,e.ɵx=be,e.ɵy=he,e.ɵz=Ce,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var y=function(e,t){return(y=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 b(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}y(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function h(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 C(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function v(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 F,x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-node-empty-config",template:"
"}),C("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.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return b(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=h([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'}),C("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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),I=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.prepareOutputConfig=function(e){return e.customerNamePattern=e.customerNamePattern.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),N=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=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 b(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){var t=this;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,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((function(e){e?t.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):t.createAlarmConfigForm.get("severity").patchValue(t.alarmSeverities[0],{emitEvent:!1})}))},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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.use-dynamically-change-the-severity-of-alar\' | translate }}\n \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 {{ \'tb.rulenode.alarm-severity\' | translate }}\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \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 tb.rulenode.relation-types-list-hint\n \n
\n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],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.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return b(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,i.Validators.pattern(/.*\S.*/)]):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,i.Validators.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.prototype.prepareOutputConfig=function(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("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}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=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 b(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,i.Validators.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.prototype.prepareOutputConfig=function(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),P=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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'}),C("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"}(F||(F={}));var M,w=new Map([[F.CUSTOMER,"tb.rulenode.originator-customer"],[F.TENANT,"tb.rulenode.originator-tenant"],[F.RELATED,"tb.rulenode.originator-related"],[F.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,D=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,K=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 B,G=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"}(B||(B={}));var H,U,j,z=new Map([[B.TITLE,"tb.rulenode.entity-details-title"],[B.COUNTRY,"tb.rulenode.entity-details-country"],[B.STATE,"tb.rulenode.entity-details-state"],[B.ZIP,"tb.rulenode.entity-details-zip"],[B.ADDRESS,"tb.rulenode.entity-details-address"],[B.ADDRESS2,"tb.rulenode.entity-details-address2"],[B.PHONE,"tb.rulenode.entity-details-phone"],[B.EMAIL,"tb.rulenode.entity-details-email"],[B.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(H||(H={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(j||(j={}));var Q,_=new Map([[j.STANDARD,"tb.rulenode.sqs-queue-standard"],[j.FIFO,"tb.rulenode.sqs-queue-fifo"]]),$=["anonymous","basic","cert.PEM"],W=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),J=["sas","cert.PEM"],Y=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"}(Q||(Q={}));var Z=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],X=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"]]),ee=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=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(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=h([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'}),C("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 b(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=h([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'}),C("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 b(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=h([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.prototype.prepareOutputConfig=function(e){return e.tableName=e.tableName.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.custom-table-hint\n \n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ae=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 b(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=v(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",String)],r.prototype,"requiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyRequiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valRequiredText",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=h([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\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}"]}),C("design:paramtypes",[o.Store,n.TranslateService,t.Injector,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.entityType=a.EntityType,n.propagateChange=null,n}var n;return b(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([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}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=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 b(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([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}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),le=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=v(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 b(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}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",String)],r.prototype,"label",void 0),h([t.Input(),C("design:type",Object)],r.prototype,"placeholder",void 0),h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.ViewChild("chipList",{static:!1}),C("design:type",d.MatChipList)],r.prototype,"chipList",void 0),h([t.ViewChild("messageTypeAutocomplete",{static:!1}),C("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),h([t.ViewChild("messageTypeInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=h([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}]}),C("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.subscriptions=[],n.disableCertPemCredentials=!1,n.allCredentialsTypes=$,n.credentialsTypeTranslationsMap=W,n.propagateChange=null,n}var n;return b(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.credentialsConfigFormGroup=this.fb.group({type:[null,[i.Validators.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(f.distinctUntilChanged()).subscribe((function(){e.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((function(){e.credentialsTypeChanged()})))},r.prototype.ngOnChanges=function(e){var t,r,n=this;try{for(var a=v(Object.keys(e)),o=a.next();!o.done;o=a.next()){var i=o.value,l=e[i];if(!l.firstChange&&l.currentValue!==l.previousValue)if(l.currentValue&&"disableCertPemCredentials"===i)"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((function(){n.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}},r.prototype.ngOnDestroy=function(){this.subscriptions.forEach((function(e){return e.unsubscribe()}))},r.prototype.writeValue=function(e){s.isDefinedAndNotNull(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))},r.prototype.setDisabledState=function(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())},r.prototype.updateView=function(){var e=this.credentialsConfigFormGroup.value,t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.validate=function(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}},r.prototype.credentialsTypeChanged=function(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()},r.prototype.updateValidators=function(e){void 0===e&&(e=!1);var t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([i.Validators.required]),this.credentialsConfigFormGroup.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(i.Validators.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})},r.prototype.requiredFilesSelected=function(e,t){return void 0===t&&(t=null),function(r){return t||(t=[Object.keys(r.controls)]),(null==r?void 0:r.controls)&&t.some((function(t){return t.every((function(t){return!e(r.controls[t])}))}))?null:{notAllRequiredFilesSelected:!0}}},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",Object)],r.prototype,"disableCertPemCredentials",void 0),r=n=h([t.Component({selector:"tb-credentials-config",template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.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
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n
\n
\n
\n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),me=function(){function e(e){this.sanitizer=e}return e.prototype.transform=function(e){return this.sanitizer.bypassSecurityTrustHtml(e)},e.ctorParameters=function(){return[{type:g.DomSanitizer}]},e=h([t.Pipe({name:"safeHtml"}),C("design:paramtypes",[g.DomSanitizer])],e)}(),ue=function(){function e(){}return e=h([t.NgModule({declarations:[ae,oe,ie,le,se,me],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ae,oe,ie,le,se,me]})],e)}(),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.prepareOutputConfig=function(e){return e.customerNamePattern=e.customerNamePattern.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("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 b(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=h([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'}),C("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.sqsQueueType=j,n.sqsQueueTypes=Object.keys(j),n.sqsQueueTypeTranslationsMap=_,n}return b(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=h([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'}),C("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}return b(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=h([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'}),C("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.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Z,n.ToByteStandartCharsetTypeTranslationMap=X,n}return b(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=h([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 \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'}),C("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}return b(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:[e?e.credentials:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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'}),C("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.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return b(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=h([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'}),C("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.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(Q),n}return b(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,[]],credentials:[e?e.credentials: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}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\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
\n'}),C("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.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return b(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=h([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'}),C("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.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return b(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=h([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),C("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.allAzureIotHubCredentialsTypes=J,n.azureIotHubCredentialsTypeTranslationsMap=Y,n}return b(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=h([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}"]}),C("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}return b(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=h([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'}),C("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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(){function e(){}return e=h([t.NgModule({declarations:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,ue],exports:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te]})],e)}(),Se=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 b(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=h([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}"]}),C("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.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=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=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),C("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.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 b(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=h([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}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ae=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Le=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=v(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 b(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}]},h([t.ViewChild("alarmStatusInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=h([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'}),C("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(){function e(){}return e=h([t.NgModule({declarations:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le],imports:[r.CommonModule,a.SharedModule,ue],exports:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le]})],e)}(),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=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=z,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=v(Object.keys(B)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(B[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 b(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(z.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(z.get(B[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}]},h([t.ViewChild("detailsInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=h([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}"]}),C("design:paramtypes",[o.Store,n.TranslateService,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=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(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=h([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
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("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.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(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=h([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
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("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 b(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=h([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=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=H,n.fetchModes=Object.keys(H),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(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===H.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=h([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}"]}),C("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}return b(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=h([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'}),C("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}return b(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=h([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("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.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(r,e),r.prototype.configForm=function(){return this.calculateDeltaConfigForm},r.prototype.onConfigurationSet=function(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e?e.inputValueKey:null,[i.Validators.required]],outputValueKey:[e?e.outputValueKey:null,[i.Validators.required]],useCache:[e?e.useCache:null,[]],addPeriodBetweenMsgs:[!!e&&e.addPeriodBetweenMsgs,[]],periodValueKey:[e?e.periodValueKey:null,[]],round:[e?e.round:null,[i.Validators.min(0),i.Validators.max(15)]],tellFailureIfDeltaIsNegative:[e?e.tellFailureIfDeltaIsNegative:null,[]]})},r.prototype.updateValidators=function(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([i.Validators.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})},r.prototype.validatorTriggers=function(){return["addPeriodBetweenMsgs"]},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-calculate-delta-config",template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(){function e(){}return e=h([t.NgModule({declarations:[Me,we,Re,De,Oe,Ke,Be,Ge,He],imports:[r.CommonModule,a.SharedModule,ue],exports:[Me,we,Re,De,Oe,Ke,Be,Ge,He]})],e)}(),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=F,n.originatorSources=Object.keys(F),n.originatorSourceTranslationMap=w,n}return b(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===F.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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ze=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),_e=function(){function e(){}return e=h([t.NgModule({declarations:[je,ze,Qe],imports:[r.CommonModule,a.SharedModule,ue],exports:[je,ze,Qe]})],e)}(),$e=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-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","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-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","shared-attributes":"Shared attributes","server-attributes":"Server attributes","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","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-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 period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","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","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',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",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","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"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-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","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":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client 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 interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","use-dynamically-change-the-severity-of-alar":"Use dynamically change the severity of alarm","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","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","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"alarm-severity-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)'},"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=h([t.NgModule({declarations:[x],imports:[r.CommonModule,a.SharedModule],exports:[qe,Pe,Ue,_e,x]}),C("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=$e,e.ɵa=x,e.ɵb=qe,e.ɵba=ve,e.ɵbb=Fe,e.ɵbc=xe,e.ɵbd=Te,e.ɵbe=ue,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=le,e.ɵbj=se,e.ɵbk=me,e.ɵbl=Pe,e.ɵbm=Se,e.ɵbn=Ie,e.ɵbo=ke,e.ɵbp=Ne,e.ɵbq=Ve,e.ɵbr=Ee,e.ɵbs=Ae,e.ɵbt=Le,e.ɵbu=Ue,e.ɵbv=Me,e.ɵbw=we,e.ɵbx=Re,e.ɵby=De,e.ɵbz=Oe,e.ɵc=T,e.ɵca=Ke,e.ɵcb=Be,e.ɵcc=Ge,e.ɵcd=He,e.ɵce=_e,e.ɵcf=je,e.ɵcg=ze,e.ɵch=Qe,e.ɵd=q,e.ɵe=S,e.ɵf=I,e.ɵg=k,e.ɵh=N,e.ɵi=V,e.ɵj=E,e.ɵk=A,e.ɵl=L,e.ɵm=P,e.ɵn=ee,e.ɵo=te,e.ɵp=re,e.ɵq=ne,e.ɵr=de,e.ɵs=pe,e.ɵt=ce,e.ɵu=fe,e.ɵv=ge,e.ɵw=ye,e.ɵx=be,e.ɵy=he,e.ɵz=Ce,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index cded6530f5..0b800eb22c 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -384,11 +384,131 @@ public class TbAlarmNodeTest { assertEquals(expectedAlarm, actualAlarm); } + @Test + public void testCreateAlarmWithDynamicSeverityFromMessageBody() throws Exception { + TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); + config.setPropagate(true); + config.setSeverity("$[alarmSeverity]"); + config.setAlarmType("SomeType"); + config.setAlarmDetailsBuildJs("DETAILS"); + config.setDynamicSeverity(true); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + + when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); + + when(ctx.getTenantId()).thenReturn(tenantId); + when(ctx.getAlarmService()).thenReturn(alarmService); + when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); + + node = new TbCreateAlarmNode(); + node.init(ctx, nodeConfiguration); + + String rawJson = "{\"alarmSeverity\": \"WARNING\", \"passed\": 5}"; + metaData.putValue("key", "value"); + TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); + when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null)); + doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class)); + + node.onMsg(ctx, msg); + + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); + verify(ctx).tellNext(any(), eq("Created")); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + + assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(originator, originatorCaptor.getValue()); + assertEquals("value", metadataCaptor.getValue().getValue("key")); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertNotSame(metaData, metadataCaptor.getValue()); + + Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class); + Alarm expectedAlarm = Alarm.builder() + .tenantId(tenantId) + .originator(originator) + .status(ACTIVE_UNACK) + .severity(WARNING) + .propagate(true) + .type("SomeType") + .details(null) + .build(); + + assertEquals(expectedAlarm, actualAlarm); + } + + @Test + public void testCreateAlarmWithDynamicSeverityFromMetadata() throws Exception { + TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); + config.setPropagate(true); + config.setSeverity("${alarmSeverity}"); + config.setAlarmType("SomeType"); + config.setAlarmDetailsBuildJs("DETAILS"); + config.setDynamicSeverity(true); + ObjectMapper mapper = new ObjectMapper(); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + + when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); + + when(ctx.getTenantId()).thenReturn(tenantId); + when(ctx.getAlarmService()).thenReturn(alarmService); + when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); + + node = new TbCreateAlarmNode(); + node.init(ctx, nodeConfiguration); + + metaData.putValue("alarmSeverity", "WARNING"); + TbMsg msg = TbMsg.newMsg("USER", originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); + + when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); + when(alarmService.findLatestByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(Futures.immediateFuture(null)); + doAnswer((Answer) invocationOnMock -> (Alarm) (invocationOnMock.getArguments())[0]).when(alarmService).createOrUpdateAlarm(any(Alarm.class)); + + node.onMsg(ctx, msg); + + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); + verify(ctx).tellNext(any(), eq("Created")); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor typeCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor originatorCaptor = ArgumentCaptor.forClass(EntityId.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + + assertEquals("ALARM", typeCaptor.getValue()); + assertEquals(originator, originatorCaptor.getValue()); + assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(IS_NEW_ALARM)); + assertNotSame(metaData, metadataCaptor.getValue()); + + Alarm actualAlarm = new ObjectMapper().readValue(dataCaptor.getValue().getBytes(), Alarm.class); + Alarm expectedAlarm = Alarm.builder() + .tenantId(tenantId) + .originator(originator) + .status(ACTIVE_UNACK) + .severity(WARNING) + .propagate(true) + .type("SomeType") + .details(null) + .build(); + + assertEquals(expectedAlarm, actualAlarm); + } + private void initWithCreateAlarmScript() { try { TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); config.setPropagate(true); - config.setSeverity(CRITICAL); + config.setSeverity(CRITICAL.name()); config.setAlarmType("SomeType"); config.setAlarmDetailsBuildJs("DETAILS"); ObjectMapper mapper = new ObjectMapper(); From f55f0ecb94893df363491106b6e3731780930409 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 21 Apr 2021 19:56:09 +0300 Subject: [PATCH 045/127] implemented firmware update for the http and mqtt transports --- .../transport/DefaultTransportApiService.java | 65 ++++++++-- .../src/main/resources/thingsboard.yml | 5 +- .../firmware/AbstractRedisFirmwareCache.java | 33 ++++++ .../firmware/CaffeineFirmwareCacheReader.java | 60 ++++++++++ .../firmware/CaffeineFirmwareCacheWriter.java | 38 ++++++ .../cache/firmware/FirmwareCacheReader.java | 22 ++++ .../cache/firmware/FirmwareCacheWriter.java | 20 ++++ .../firmware/RedisFirmwareCacheReader.java | 49 ++++++++ .../firmware/RedisFirmwareCacheWriter.java | 38 ++++++ .../server/common/data/CacheConstants.java | 1 + .../data/device/profile/MqttTopics.java | 10 ++ common/queue/src/main/proto/queue.proto | 22 +++- .../transport/coap/CoapTransportResource.java | 2 +- .../transport/http/DeviceApiController.java | 65 +++++++++- .../store/TbLwM2mRedisSecurityStore.java | 1 - .../transport/mqtt/MqttTransportHandler.java | 111 +++++++++++++++++- .../mqtt/adaptors/JsonMqttAdaptor.java | 10 +- .../mqtt/adaptors/MqttTransportAdaptor.java | 18 +++ .../mqtt/adaptors/ProtoMqttAdaptor.java | 22 +--- .../common/transport/TransportContext.java | 8 +- .../common/transport/TransportService.java | 4 + .../transport/adaptor/JsonConverter.java | 12 +- .../service/DefaultTransportService.java | 18 ++- .../dao/firmware/BaseFirmwareService.java | 19 ++- 24 files changed, 603 insertions(+), 50 deletions(-) create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java create mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java 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 9613d708d2..768eb592f4 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 @@ -23,16 +23,19 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cache.firmware.FirmwareCacheWriter; import org.thingsboard.server.common.data.ApiUsageState; 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.EntityType; -import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; @@ -40,6 +43,7 @@ import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileC import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.DeviceCredentials; @@ -55,6 +59,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; +import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.resource.TbResourceService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @@ -66,7 +71,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFro import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; -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; @@ -109,6 +113,8 @@ public class DefaultTransportApiService implements TransportApiService { private final DataDecodingEncodingService dataDecodingEncodingService; private final DeviceProvisionService deviceProvisionService; private final TbResourceService resourceService; + private final FirmwareService firmwareService; + private final FirmwareCacheWriter firmwareCacheWriter; private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); @@ -117,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService { RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, - DeviceProvisionService deviceProvisionService, TbResourceService resourceService) { + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, CacheManager cacheManager, FirmwareCacheWriter firmwareCacheWriter) { this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; this.apiUsageStateService = apiUsageStateService; @@ -130,6 +136,8 @@ public class DefaultTransportApiService implements TransportApiService { this.dataDecodingEncodingService = dataDecodingEncodingService; this.deviceProvisionService = deviceProvisionService; this.resourceService = resourceService; + this.firmwareService = firmwareService; + this.firmwareCacheWriter = firmwareCacheWriter; } @Override @@ -166,6 +174,9 @@ public class DefaultTransportApiService implements TransportApiService { } else if (transportApiRequestMsg.hasResourceRequestMsg()) { return Futures.transform(handle(transportApiRequestMsg.getResourceRequestMsg()), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); + } else if (transportApiRequestMsg.hasFirmwareRequestMsg()) { + return Futures.transform(handle(transportApiRequestMsg.getFirmwareRequestMsg()), + value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); } return Futures.transform(getEmptyTransportApiResponseFuture(), value -> new TbProtoQueueMsg<>(tbProtoQueueMsg.getKey(), value, tbProtoQueueMsg.getHeaders()), MoreExecutors.directExecutor()); @@ -313,14 +324,14 @@ public class DefaultTransportApiService implements TransportApiService { } catch (ProvisionFailedException e) { return Futures.immediateFuture(getTransportApiResponseMsg( new DeviceCredentials(), - TransportProtos.ProvisionResponseStatus.valueOf(e.getMessage()))); + TransportProtos.ResponseStatus.valueOf(e.getMessage()))); } - return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ProvisionResponseStatus.SUCCESS), + return Futures.transform(provisionResponseFuture, provisionResponse -> getTransportApiResponseMsg(provisionResponse.getDeviceCredentials(), TransportProtos.ResponseStatus.SUCCESS), dbCallbackExecutorService); } - private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ProvisionResponseStatus status) { - if (!status.equals(ProvisionResponseStatus.SUCCESS)) { + private TransportApiResponseMsg getTransportApiResponseMsg(DeviceCredentials deviceCredentials, TransportProtos.ResponseStatus status) { + if (!status.equals(TransportProtos.ResponseStatus.SUCCESS)) { return TransportApiResponseMsg.newBuilder().setProvisionDeviceResponseMsg(TransportProtos.ProvisionDeviceResponseMsg.newBuilder().setStatus(status).build()).build(); } TransportProtos.ProvisionDeviceResponseMsg.Builder provisionResponse = TransportProtos.ProvisionDeviceResponseMsg.newBuilder() @@ -438,6 +449,46 @@ public class DefaultTransportApiService implements TransportApiService { } } + private ListenableFuture handle(TransportProtos.GetFirmwareRequestMsg requestMsg) { + TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); + DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB())); + Device device = deviceService.findDeviceById(tenantId, deviceId); + + if (device == null) { + return getEmptyTransportApiResponseFuture(); + } + + FirmwareId firmwareId = device.getFirmwareId(); + + if (firmwareId == null) { + firmwareId = deviceProfileCache.find(device.getDeviceProfileId()).getFirmwareId(); + } + + TransportProtos.GetFirmwareResponseMsg.Builder builder = TransportProtos.GetFirmwareResponseMsg.newBuilder(); + + if (firmwareId == null) { + builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); + } else { + Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId); + + if (firmware == null) { + builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); + } else { + builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS); + builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits()); + builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()); + builder.setFileName(firmware.getFileName()); + builder.setContentType(firmware.getContentType()); + firmwareCacheWriter.put(firmwareId.toString(), firmware.getData().array()); + } + } + + return Futures.immediateFuture( + TransportApiResponseMsg.newBuilder() + .setFirmwareResponseMsg(builder.build()) + .build()); + } + private ListenableFuture handleRegistration(TransportProtos.LwM2MRegistrationRequestMsg msg) { TenantId tenantId = new TenantId(UUID.fromString(msg.getTenantId())); String deviceName = msg.getEndpoint(); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 01b2eb47b6..777d75974f 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -281,7 +281,7 @@ actors: tenant: create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}" session: - max_concurrent_sessions_per_device: "${ACTORS_MAX_CONCURRENT_SESSION_PER_DEVICE:1}" + max_concurrent_sessions_per_device: "${ACTORS_MAX_CONCURRENT_SESSION_PER_DEVICE:2}" sync: # Default timeout for processing request using synchronous session (HTTP, CoAP) in milliseconds timeout: "${ACTORS_SESSION_SYNC_TIMEOUT:10000}" @@ -365,6 +365,9 @@ caffeine: tokensOutdatageTime: timeToLiveInMinutes: 20000 maxSize: 10000 + firmwares: + timeToLiveInMinutes: 1440 + maxSize: 100 redis: # standalone or cluster diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java new file mode 100644 index 0000000000..371b85b94d --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2021 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.cache.firmware; + +import org.springframework.data.redis.connection.RedisConnectionFactory; + +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; + +public abstract class AbstractRedisFirmwareCache { + + protected final RedisConnectionFactory redisConnectionFactory; + + protected AbstractRedisFirmwareCache(RedisConnectionFactory redisConnectionFactory) { + this.redisConnectionFactory = redisConnectionFactory; + } + + protected byte[] toFirmwareCacheKey(String key) { + return String.format("%s::%s", FIRMWARE_CACHE, key).getBytes(); + } +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java new file mode 100644 index 0000000000..f7e05f818c --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2021 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.cache.firmware; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; + +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; + +@Service +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='caffeine')") +public class CaffeineFirmwareCacheReader implements FirmwareCacheReader { + + private final CacheManager cacheManager; + + public CaffeineFirmwareCacheReader(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + @Override + public byte[] get(String key) { + return get(key, 0, 0); + } + + @Override + public byte[] get(String key, int chunkSize, int chunk) { + byte[] data = cacheManager.getCache(FIRMWARE_CACHE).get(key, byte[].class); + + if (chunkSize < 1) { + return data; + } + + if (data != null && data.length > 0) { + int startIndex = chunkSize * chunk; + + int size = Math.min(data.length - startIndex, chunkSize); + + if (startIndex < data.length && size > 0) { + byte[] result = new byte[size]; + System.arraycopy(data, startIndex, result, 0, size); + return result; + } + } + return new byte[0]; + } +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java new file mode 100644 index 0000000000..e543739382 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2021 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.cache.firmware; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; + +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; + +@Service +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='core') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='caffeine')") +public class CaffeineFirmwareCacheWriter implements FirmwareCacheWriter { + + private final CacheManager cacheManager; + + public CaffeineFirmwareCacheWriter(CacheManager cacheManager) { + this.cacheManager = cacheManager; + } + + @Override + public void put(String key, byte[] value) { + cacheManager.getCache(FIRMWARE_CACHE).putIfAbsent(key, value); + } +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java new file mode 100644 index 0000000000..4b4dab4caa --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2021 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.cache.firmware; + +public interface FirmwareCacheReader { + byte[] get(String key); + + byte[] get(String key, int chunkSize, int chunk); +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java new file mode 100644 index 0000000000..f1387f9ff1 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2021 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.cache.firmware; + +public interface FirmwareCacheWriter { + void put(String key, byte[] value); +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java new file mode 100644 index 0000000000..9bf8b40230 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java @@ -0,0 +1,49 @@ +/** + * Copyright © 2016-2021 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.cache.firmware; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; + +@Service +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && '${cache.type:null}'=='redis'") +public class RedisFirmwareCacheReader extends AbstractRedisFirmwareCache implements FirmwareCacheReader { + + public RedisFirmwareCacheReader(RedisConnectionFactory redisConnectionFactory) { + super(redisConnectionFactory); + } + + @Override + public byte[] get(String key) { + return get(key, 0, 0); + } + + @Override + public byte[] get(String key, int chunkSize, int chunk) { + try (RedisConnection connection = redisConnectionFactory.getConnection()) { + if (chunkSize == 0) { + return connection.get(toFirmwareCacheKey(key)); + } + + int startIndex = chunkSize * chunk; + int endIndex = startIndex + chunkSize - 1; + return connection.getRange(toFirmwareCacheKey(key), startIndex, endIndex); + } + } + +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java new file mode 100644 index 0000000000..6ba3ee91f6 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2021 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.cache.firmware; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.data.redis.connection.RedisConnection; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; + +@Service +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='core') && '${cache.type:null}'=='redis'") +public class RedisFirmwareCacheWriter extends AbstractRedisFirmwareCache implements FirmwareCacheWriter { + + public RedisFirmwareCacheWriter(RedisConnectionFactory redisConnectionFactory) { + super(redisConnectionFactory); + } + + @Override + public void put(String key, byte[] value) { + try (RedisConnection connection = redisConnectionFactory.getConnection()) { + connection.set(toFirmwareCacheKey(key), value); + } + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index 62f625f285..ba14b24292 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -28,4 +28,5 @@ public class CacheConstants { public static final String DEVICE_PROFILE_CACHE = "deviceProfiles"; public static final String ATTRIBUTES_CACHE = "attributes"; public static final String TOKEN_OUTDATAGE_TIME_CACHE = "tokensOutdatageTime"; + public static final String FIRMWARE_CACHE = "firmwares"; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index f736fe371f..419f9c7f43 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -30,6 +30,9 @@ public class MqttTopics { private static final String CLAIM = "/claim"; private static final String SUB_TOPIC = "+"; private static final String PROVISION = "/provision"; + private static final String FIRMWARE = "/fw"; + private static final String CHUNK = "/chunk/"; + private static final String ERROR = "/error"; private static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE; private static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST; @@ -69,6 +72,13 @@ public class MqttTopics { public static final String GATEWAY_ATTRIBUTES_REQUEST_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_REQUEST; public static final String GATEWAY_ATTRIBUTES_RESPONSE_TOPIC = BASE_GATEWAY_API_TOPIC + ATTRIBUTES_RESPONSE; + // v2 topics + public static final String BASE_DEVICE_API_TOPIC_V2 = "v2"; + + public static final String DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/"; + public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + SUB_TOPIC + CHUNK + SUB_TOPIC; + public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR; + private MqttTopics() { } } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 7e9ff076f2..7abcd3613f 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -336,17 +336,33 @@ message ProvisionDeviceCredentialsMsg { } message ProvisionDeviceResponseMsg { - ProvisionResponseStatus status = 1; + ResponseStatus status = 1; CredentialsType credentialsType = 2; string credentialsValue = 3; } -enum ProvisionResponseStatus { +enum ResponseStatus { UNKNOWN = 0; SUCCESS = 1; NOT_FOUND = 2; FAILURE = 3; } + +message GetFirmwareRequestMsg { + int64 deviceIdMSB = 1; + int64 deviceIdLSB = 2; + int64 tenantIdMSB = 3; + int64 tenantIdLSB = 4; +} + +message GetFirmwareResponseMsg { + ResponseStatus responseStatus = 1; + int64 firmwareIdMSB = 2; + int64 firmwareIdLSB = 3; + string contentType = 4; + string fileName = 5; +} + //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. message SubscriptionInfoProto { int64 lastActivityTime = 1; @@ -552,6 +568,7 @@ message TransportApiRequestMsg { ProvisionDeviceRequestMsg provisionDeviceRequestMsg = 7; ValidateDeviceLwM2MCredentialsRequestMsg validateDeviceLwM2MCredentialsRequestMsg = 8; GetResourceRequestMsg resourceRequestMsg = 9; + GetFirmwareRequestMsg firmwareRequestMsg = 10; } /* Response from ThingsBoard Core Service to Transport Service */ @@ -562,6 +579,7 @@ message TransportApiResponseMsg { ProvisionDeviceResponseMsg provisionDeviceResponseMsg = 4; LwM2MResponseMsg lwM2MResponseMsg = 6; GetResourceResponseMsg resourceResponseMsg = 7; + GetFirmwareResponseMsg firmwareResponseMsg = 8; } /* Messages that are handled by ThingsBoard Core Service */ diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index c1fd5b6a4a..89e5213fd0 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -360,7 +360,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @Override public void onSuccess(TransportProtos.ProvisionDeviceResponseMsg msg) { CoAP.ResponseCode responseCode = CoAP.ResponseCode.CREATED; - if (!msg.getStatus().equals(TransportProtos.ProvisionResponseStatus.SUCCESS)) { + if (!msg.getStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { responseCode = CoAP.ResponseCode.BAD_REQUEST; } if (payloadType.equals(TransportPayloadType.JSON)) { diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index e222b690ef..5c10202d00 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -20,7 +20,10 @@ import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.PathVariable; @@ -41,7 +44,6 @@ import org.thingsboard.server.common.transport.auth.SessionInfoCreator; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; @@ -53,7 +55,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import javax.servlet.http.HttpServletRequest; @@ -204,6 +205,23 @@ public class DeviceApiController { return responseWriter; } + @RequestMapping(value = "/{deviceToken}/firmware", method = RequestMethod.GET) + public DeferredResult getFirmware(@PathVariable("deviceToken") String deviceToken, + @RequestParam(value = "chunkSize", required = false, defaultValue = "0") int chunkSize, + @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) { + DeferredResult responseWriter = new DeferredResult<>(); + transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), + new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { + TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build(); + transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, chunkSize, chunk)); + })); + return responseWriter; + } + @RequestMapping(value = "/provision", method = RequestMethod.POST) public DeferredResult provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) { DeferredResult responseWriter = new DeferredResult<>(); @@ -258,6 +276,41 @@ public class DeviceApiController { } } + private class GetFirmwareCallback implements TransportServiceCallback { + private final DeferredResult responseWriter; + private final int chuckSize; + private final int chuck; + + GetFirmwareCallback(DeferredResult responseWriter, int chuckSize, int chuck) { + this.responseWriter = responseWriter; + this.chuckSize = chuckSize; + this.chuck = chuck; + } + + @Override + public void onSuccess(TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg) { + if (!TransportProtos.ResponseStatus.SUCCESS.equals(firmwareResponseMsg.getResponseStatus())) { + responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_FOUND)); + } else { + String firmwareId = new UUID(firmwareResponseMsg.getFirmwareIdMSB(), firmwareResponseMsg.getFirmwareIdLSB()).toString(); + ByteArrayResource resource = new ByteArrayResource(transportContext.getFirmwareCacheReader().get(firmwareId, chuckSize, chuck)); + ResponseEntity response = ResponseEntity.ok() + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmwareResponseMsg.getFileName()) + .header("x-filename", firmwareResponseMsg.getFileName()) + .contentLength(resource.contentLength()) + .contentType(parseMediaType(firmwareResponseMsg.getContentType())) + .body(resource); + responseWriter.setResult(response); + } + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process request", e); + responseWriter.setResult(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); + } + } + private static class SessionCloseOnErrorCallback implements TransportServiceCallback { private final TransportService transportService; private final SessionInfoProto sessionInfo; @@ -338,4 +391,12 @@ public class DeviceApiController { .build(), TransportServiceCallback.EMPTY); } + private static MediaType parseMediaType(String contentType) { + try { + return MediaType.parseMediaType(contentType); + } catch (Exception e) { + return MediaType.APPLICATION_OCTET_STREAM; + } + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java index 7d05630ba4..fa0cf67694 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java @@ -29,7 +29,6 @@ import redis.clients.jedis.ScanResult; import java.util.Collection; import java.util.LinkedList; -@Service public class TbLwM2mRedisSecurityStore implements EditableSecurityStore { private static final String SEC_EP = "SEC#EP#"; 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 06a8dcacdc..de525d107d 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 @@ -46,6 +46,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.transport.SessionMsgListener; @@ -69,10 +70,10 @@ import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; import org.thingsboard.server.transport.mqtt.util.SslUtil; import javax.net.ssl.SSLPeerUnverifiedException; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; import java.io.IOException; import java.net.InetSocketAddress; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -80,7 +81,10 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import static com.amazonaws.util.StringUtils.UTF8; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_ACCEPTED; import static io.netty.handler.codec.mqtt.MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; import static io.netty.handler.codec.mqtt.MqttMessageType.CONNACK; @@ -99,6 +103,10 @@ import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE; @Slf4j public class MqttTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener>, SessionMsgListener { + private static final Pattern FW_PATTERN = Pattern.compile("v2/fw/request/(?\\d+)/chunk/(?\\d+)"); + + private static final String PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE"; + private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE; private final UUID sessionId; @@ -112,6 +120,9 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private volatile InetSocketAddress address; private volatile GatewaySessionHandler gatewaySessionHandler; + private final ConcurrentHashMap fwSessions; + private final ConcurrentHashMap fwChunkSizes; + MqttTransportHandler(MqttTransportContext context, SslHandler sslHandler) { this.sessionId = UUID.randomUUID(); this.context = context; @@ -120,6 +131,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement this.sslHandler = sslHandler; this.mqttQoSMap = new ConcurrentHashMap<>(); this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context); + this.fwSessions = new ConcurrentHashMap<>(); + this.fwChunkSizes = new ConcurrentHashMap<>(); } @Override @@ -280,6 +293,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private void processDevicePublish(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, String topicName, int msgId) { try { + Matcher fwMatcher; MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); @@ -299,6 +313,38 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); + } else if ((fwMatcher = FW_PATTERN.matcher(topicName)).find()) { + String payload = mqttMsg.content().toString(UTF8); + int chunkSize = payload != null ? Integer.parseInt(payload) : 0; + String requestId = fwMatcher.group("requestId"); + int chunk = Integer.parseInt(fwMatcher.group("chunk")); + + if (chunkSize > 0) { + this.fwChunkSizes.put(requestId, chunkSize); + } else { + chunkSize = fwChunkSizes.getOrDefault(requestId, 0); + } + + if (chunkSize > context.getMaxPayloadSize()) { + sendFirmwareError(ctx, PAYLOAD_TOO_LARGE); + return; + } + + String firmwareId = fwSessions.get(requestId); + + if (firmwareId != null) { + sendFirmware(ctx, mqttMsg.variableHeader().packetId(), firmwareId, requestId, chunkSize, chunk); + } else { + TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo(); + TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .build(); + transportService.process(deviceSessionCtx.getSessionInfo(), getFirmwareRequestMsg, + new FirmwareCallback(ctx, mqttMsg.variableHeader().packetId(), getFirmwareRequestMsg, requestId, chunkSize, chunk)); + } } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); ack(ctx, msgId); @@ -366,6 +412,65 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private class FirmwareCallback implements TransportServiceCallback { + private final ChannelHandlerContext ctx; + private final int msgId; + private final TransportProtos.GetFirmwareRequestMsg msg; + private final String requestId; + private final int chunkSize; + private final int chunk; + + FirmwareCallback(ChannelHandlerContext ctx, int msgId, TransportProtos.GetFirmwareRequestMsg msg, String requestId, int chunkSize, int chunk) { + this.ctx = ctx; + this.msgId = msgId; + this.msg = msg; + this.requestId = requestId; + this.chunkSize = chunkSize; + this.chunk = chunk; + } + + @Override + public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) { + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { + FirmwareId firmwareId = new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())); + fwSessions.put(requestId, firmwareId.toString()); + sendFirmware(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk); + } else { + sendFirmwareError(ctx, response.getResponseStatus().toString()); + } + } + + @Override + public void onError(Throwable e) { + log.trace("[{}] Failed to get firmware: {}", sessionId, msg, e); + processDisconnect(ctx); + } + } + + private void sendFirmware(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk) { + log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId); + ack(ctx, msgId); + try { + byte[] firmwareChunk = context.getFirmwareCacheReader().get(firmwareId, chunkSize, chunk); + deviceSessionCtx.getPayloadAdaptor() + .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk) + .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); + if (firmwareChunk != null && chunkSize != firmwareChunk.length) { + scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); + } + } catch (Exception e) { + log.trace("[{}] Failed to send firmware response!", sessionId, e); + } + } + + private void sendFirmwareError(ChannelHandlerContext ctx, String error) { + log.warn("[{}] {}", sessionId, error); + deviceSessionCtx.getChannel().writeAndFlush(deviceSessionCtx + .getPayloadAdaptor() + .createMqttPublishMsg(deviceSessionCtx, MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC, error.getBytes())); + processDisconnect(ctx); + } + private void processSubscribe(ChannelHandlerContext ctx, MqttSubscribeMessage mqttMsg) { if (!checkConnected(ctx, mqttMsg)) { return; @@ -396,6 +501,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.GATEWAY_RPC_TOPIC: case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC: case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: + case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC: + case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC: registerSubQoS(topic, grantedQoSList, reqQoS); break; default: diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 47a3a9fe87..c7690b756f 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -21,8 +21,6 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonSyntaxException; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufAllocator; -import io.netty.buffer.UnpooledByteBufAllocator; import io.netty.handler.codec.mqtt.MqttFixedHeader; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttMessageType; @@ -31,10 +29,10 @@ import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; import lombok.extern.slf4j.Slf4j; 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.gen.transport.TransportProtos; -import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionContext; import java.nio.charset.Charset; @@ -55,7 +53,6 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { protected static final Charset UTF8 = StandardCharsets.UTF_8; private static final Gson GSON = new Gson(); - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); @Override public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { @@ -153,6 +150,11 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, JsonConverter.toJson(provisionResponse))); } + @Override + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) { + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/" + chunk, firmwareChunk)); + } + public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { String payload = validatePayload(sessionId, payloadData, false); try { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java index e8a455a923..2bd35df8d9 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -15,8 +15,14 @@ */ package org.thingsboard.server.transport.mqtt.adaptors; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.UnpooledByteBufAllocator; +import io.netty.handler.codec.mqtt.MqttFixedHeader; import io.netty.handler.codec.mqtt.MqttMessage; +import io.netty.handler.codec.mqtt.MqttMessageType; import io.netty.handler.codec.mqtt.MqttPublishMessage; +import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -39,6 +45,8 @@ import java.util.Optional; */ public interface MqttTransportAdaptor { + ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); + PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; PostAttributeMsg convertToPostAttributes(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException; @@ -69,4 +77,14 @@ public interface MqttTransportAdaptor { Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException; + + default MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadInBytes) { + MqttFixedHeader mqttFixedHeader = + new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); + MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId()); + ByteBuf payload = ALLOCATOR.buffer(); + payload.writeBytes(payloadInBytes); + return new MqttPublishMessage(mqttFixedHeader, header, payload); + } } 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 a004fbd500..82287bfdce 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 @@ -21,13 +21,8 @@ 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; -import io.netty.handler.codec.mqtt.MqttFixedHeader; import io.netty.handler.codec.mqtt.MqttMessage; -import io.netty.handler.codec.mqtt.MqttMessageType; import io.netty.handler.codec.mqtt.MqttPublishMessage; -import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -46,8 +41,6 @@ import java.util.Optional; @Slf4j public class ProtoMqttAdaptor implements MqttTransportAdaptor { - private static final ByteBufAllocator ALLOCATOR = new UnpooledByteBufAllocator(false); - @Override public TransportProtos.PostTelemetryMsg convertToPostTelemetry(MqttDeviceAwareSessionContext ctx, MqttPublishMessage inbound) throws AdaptorException { DeviceSessionCtx deviceSessionCtx = (DeviceSessionCtx) ctx; @@ -143,7 +136,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { } } - @Override public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, TransportProtos.ToDeviceRpcRequestMsg rpcRequest) { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_RPC_REQUESTS_TOPIC + rpcRequest.getRequestId(), rpcRequest.toByteArray())); @@ -164,6 +156,11 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC, provisionResponse.toByteArray())); } + @Override + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException { + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/" + chunk, firmwareChunk)); + } + @Override public Optional convertToGatewayPublish(MqttDeviceAwareSessionContext ctx, String deviceName, TransportProtos.GetAttributeResponseMsg responseMsg) throws AdaptorException { if (!StringUtils.isEmpty(responseMsg.getError())) { @@ -202,15 +199,6 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { return bytes; } - private MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadBytes) { - MqttFixedHeader mqttFixedHeader = - new MqttFixedHeader(MqttMessageType.PUBLISH, false, ctx.getQoSForTopic(topic), false, 0); - MqttPublishVariableHeader header = new MqttPublishVariableHeader(topic, ctx.nextMsgId()); - ByteBuf payload = ALLOCATOR.buffer(); - payload.writeBytes(payloadBytes); - return new MqttPublishMessage(mqttFixedHeader, header, payload); - } - private int getRequestId(String topicName, String topic) { return Integer.parseInt(topicName.substring(topic.length())); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index 75f20cc825..a1d09b78a7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -20,10 +20,9 @@ import lombok.Data; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.firmware.FirmwareCacheReader; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -51,6 +50,11 @@ public abstract class TransportContext { @Getter private ExecutorService executor; + + @Getter + @Autowired + private FirmwareCacheReader firmwareCacheReader; + @PostConstruct public void init() { executor = Executors.newWorkStealingPool(50); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 04b08272b1..c851be2586 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -24,6 +24,8 @@ import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetFirmwareRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetFirmwareResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetResourceResponseMsg; @@ -98,6 +100,8 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); + void process(SessionInfoProto sessionInfoProto, GetFirmwareRequestMsg msg, TransportServiceCallback callback); + SessionMetaData registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); SessionMetaData registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); 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 2c5dfafe4e..680ad0e837 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 @@ -44,7 +44,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.KeyValueType; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ProvisionResponseStatus; +import org.thingsboard.server.gen.transport.TransportProtos.ResponseStatus; import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; @@ -423,12 +423,12 @@ public class JsonConverter { private static JsonObject toJson(ProvisionDeviceResponseMsg payload, boolean toGateway, int requestId) { JsonObject result = new JsonObject(); - if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.NOT_FOUND) { + if (payload.getStatus() == ResponseStatus.NOT_FOUND) { result.addProperty("errorMsg", "Provision data was not found!"); - result.addProperty("status", ProvisionResponseStatus.NOT_FOUND.name()); - } else if (payload.getStatus() == TransportProtos.ProvisionResponseStatus.FAILURE) { + result.addProperty("status", ResponseStatus.NOT_FOUND.name()); + } else if (payload.getStatus() == TransportProtos.ResponseStatus.FAILURE) { result.addProperty("errorMsg", "Failed to provision device!"); - result.addProperty("status", ProvisionResponseStatus.FAILURE.name()); + result.addProperty("status", ResponseStatus.FAILURE.name()); } else { if (toGateway) { result.addProperty("id", requestId); @@ -445,7 +445,7 @@ public class JsonConverter { break; } result.addProperty("credentialsType", payload.getCredentialsType().name()); - result.addProperty("status", ProvisionResponseStatus.SUCCESS.name()); + result.addProperty("status", ResponseStatus.SUCCESS.name()); } return result; } 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 740a2a622f..fa28627882 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 @@ -377,7 +377,6 @@ public class DefaultTransportService implements TransportService { AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } - @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback callback) { if (log.isTraceEnabled()) { @@ -529,6 +528,19 @@ public class DefaultTransportService implements TransportService { } } + @Override + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetFirmwareRequestMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, msg, callback)) { + TbProtoQueueMsg protoMsg = + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setFirmwareRequestMsg(msg).build()); + + AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), response -> { + TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg = response.getValue().getFirmwareResponseMsg(); + callback.onSuccess(firmwareResponseMsg); + }, callback::onError, transportCallbackExecutor); + } + } + @Override public SessionMetaData reportActivity(TransportProtos.SessionInfoProto sessionInfo) { return reportActivityInternal(sessionInfo); @@ -608,11 +620,11 @@ public class DefaultTransportService implements TransportService { sessions.remove(toSessionId(sessionInfo)); } - private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { return checkLimits(sessionInfo, msg, callback, 0); } - private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints) { + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 195f31bd1d..df3291b486 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -20,6 +20,8 @@ import com.google.common.hash.Hashing; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; @@ -34,8 +36,10 @@ import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TenantDao; import java.nio.ByteBuffer; +import java.util.Collections; import java.util.Optional; +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; import static org.thingsboard.server.dao.service.Validator.validateId; import static org.thingsboard.server.dao.service.Validator.validatePageLink; @@ -48,11 +52,13 @@ public class BaseFirmwareService implements FirmwareService { private final TenantDao tenantDao; private final FirmwareDao firmwareDao; private final FirmwareInfoDao firmwareInfoDao; + private final CacheManager cacheManager; - public BaseFirmwareService(TenantDao tenantDao, FirmwareDao firmwareDao, FirmwareInfoDao firmwareInfoDao) { + public BaseFirmwareService(TenantDao tenantDao, FirmwareDao firmwareDao, FirmwareInfoDao firmwareInfoDao, CacheManager cacheManager) { this.tenantDao = tenantDao; this.firmwareDao = firmwareDao; this.firmwareInfoDao = firmwareInfoDao; + this.cacheManager = cacheManager; } @Override @@ -60,6 +66,10 @@ public class BaseFirmwareService implements FirmwareService { log.trace("Executing saveFirmwareInfo [{}]", firmwareInfo); firmwareInfoValidator.validate(firmwareInfo, FirmwareInfo::getTenantId); try { + FirmwareId firmwareId = firmwareInfo.getId(); + if (firmwareId != null) { + cacheManager.getCache(FIRMWARE_CACHE).evict(firmwareId.toString()); + } return firmwareInfoDao.save(firmwareInfo.getTenantId(), firmwareInfo); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); @@ -76,6 +86,10 @@ public class BaseFirmwareService implements FirmwareService { log.trace("Executing saveFirmware [{}]", firmware); firmwareValidator.validate(firmware, FirmwareInfo::getTenantId); try { + FirmwareId firmwareId = firmware.getId(); + if (firmwareId != null) { + cacheManager.getCache(FIRMWARE_CACHE).evict(firmwareId.toString()); + } return firmwareDao.save(firmware.getTenantId(), firmware); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); @@ -122,6 +136,8 @@ public class BaseFirmwareService implements FirmwareService { log.trace("Executing deleteFirmware [{}]", firmwareId); validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); try { + Cache cache = cacheManager.getCache(FIRMWARE_CACHE); + cache.evict(Collections.singletonList(firmwareId)); firmwareDao.removeById(tenantId, firmwareId.getId()); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); @@ -278,7 +294,6 @@ public class BaseFirmwareService implements FirmwareService { private PaginatedRemover tenantFirmwareRemover = new PaginatedRemover<>() { - @Override protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { return firmwareInfoDao.findFirmwareInfoByTenantId(id, pageLink); From 6c5cf107dfe0e216e20c4f9813878302bf6608fe Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 21 Apr 2021 20:07:49 +0300 Subject: [PATCH 046/127] added firmwares cache to the application-test.properties --- dao/src/test/resources/application-test.properties | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index 36d73a96ca..6760f96631 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -36,6 +36,9 @@ caffeine.specs.tenantProfiles.maxSize=100000 caffeine.specs.deviceProfiles.timeToLiveInMinutes=1440 caffeine.specs.deviceProfiles.maxSize=100000 +caffeine.specs.firmwares.timeToLiveInMinutes=1440 +caffeine.specs.firmwares.maxSize=100000 + redis.connection.host=localhost redis.connection.port=6379 redis.connection.db=0 From 75fe679bab371695a85f15d519ca47553c424c9d Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 22 Apr 2021 08:16:17 +0300 Subject: [PATCH 047/127] MqttTransportHandler improvements --- .../server/transport/mqtt/MqttTransportHandler.java | 3 ++- .../server/transport/mqtt/adaptors/JsonMqttAdaptor.java | 2 +- 2 files changed, 3 insertions(+), 2 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 de525d107d..624e2410b1 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.ReferenceCountUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -315,7 +316,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); } else if ((fwMatcher = FW_PATTERN.matcher(topicName)).find()) { String payload = mqttMsg.content().toString(UTF8); - int chunkSize = payload != null ? Integer.parseInt(payload) : 0; + int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0; String requestId = fwMatcher.group("requestId"); int chunk = Integer.parseInt(fwMatcher.group("chunk")); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index c7690b756f..8d0bf312a0 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -152,7 +152,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { @Override public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) { - return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/" + chunk, firmwareChunk)); + return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/chunk/" + chunk, firmwareChunk)); } public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { From 9900cc3d97407ef9b31b5b6ee3c7905537e1602c Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 21 Apr 2021 18:37:51 +0300 Subject: [PATCH 048/127] Updated ui rule node --- .../resources/public/static/rulenode/rulenode-core-config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 810ccb3c18..3d86444d63 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 @@ -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 y=function(e,t){return(y=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 b(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}y(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function h(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 C(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function v(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 F,x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-node-empty-config",template:"
"}),C("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.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return b(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=h([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'}),C("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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),I=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.prepareOutputConfig=function(e){return e.customerNamePattern=e.customerNamePattern.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),N=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=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 b(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){var t=this;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,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((function(e){e?t.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):t.createAlarmConfigForm.get("severity").patchValue(t.alarmSeverities[0],{emitEvent:!1})}))},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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.use-dynamically-change-the-severity-of-alar\' | translate }}\n \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 {{ \'tb.rulenode.alarm-severity\' | translate }}\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \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 tb.rulenode.relation-types-list-hint\n \n
\n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],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.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return b(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,i.Validators.pattern(/.*\S.*/)]):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,i.Validators.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.prototype.prepareOutputConfig=function(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("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}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=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 b(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,i.Validators.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.prototype.prepareOutputConfig=function(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),P=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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'}),C("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"}(F||(F={}));var M,w=new Map([[F.CUSTOMER,"tb.rulenode.originator-customer"],[F.TENANT,"tb.rulenode.originator-tenant"],[F.RELATED,"tb.rulenode.originator-related"],[F.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,D=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,K=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 B,G=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"}(B||(B={}));var H,U,j,z=new Map([[B.TITLE,"tb.rulenode.entity-details-title"],[B.COUNTRY,"tb.rulenode.entity-details-country"],[B.STATE,"tb.rulenode.entity-details-state"],[B.ZIP,"tb.rulenode.entity-details-zip"],[B.ADDRESS,"tb.rulenode.entity-details-address"],[B.ADDRESS2,"tb.rulenode.entity-details-address2"],[B.PHONE,"tb.rulenode.entity-details-phone"],[B.EMAIL,"tb.rulenode.entity-details-email"],[B.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(H||(H={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(j||(j={}));var Q,_=new Map([[j.STANDARD,"tb.rulenode.sqs-queue-standard"],[j.FIFO,"tb.rulenode.sqs-queue-fifo"]]),$=["anonymous","basic","cert.PEM"],W=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),J=["sas","cert.PEM"],Y=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"}(Q||(Q={}));var Z=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],X=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"]]),ee=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=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(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=h([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'}),C("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 b(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=h([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'}),C("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 b(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=h([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.prototype.prepareOutputConfig=function(e){return e.tableName=e.tableName.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.custom-table-hint\n \n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ae=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 b(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=v(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",String)],r.prototype,"requiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyRequiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valRequiredText",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=h([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\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}"]}),C("design:paramtypes",[o.Store,n.TranslateService,t.Injector,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.entityType=a.EntityType,n.propagateChange=null,n}var n;return b(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([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}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=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 b(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([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}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),le=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=v(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 b(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}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",String)],r.prototype,"label",void 0),h([t.Input(),C("design:type",Object)],r.prototype,"placeholder",void 0),h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.ViewChild("chipList",{static:!1}),C("design:type",d.MatChipList)],r.prototype,"chipList",void 0),h([t.ViewChild("messageTypeAutocomplete",{static:!1}),C("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),h([t.ViewChild("messageTypeInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=h([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}]}),C("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.subscriptions=[],n.disableCertPemCredentials=!1,n.allCredentialsTypes=$,n.credentialsTypeTranslationsMap=W,n.propagateChange=null,n}var n;return b(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.credentialsConfigFormGroup=this.fb.group({type:[null,[i.Validators.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(f.distinctUntilChanged()).subscribe((function(){e.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((function(){e.credentialsTypeChanged()})))},r.prototype.ngOnChanges=function(e){var t,r,n=this;try{for(var a=v(Object.keys(e)),o=a.next();!o.done;o=a.next()){var i=o.value,l=e[i];if(!l.firstChange&&l.currentValue!==l.previousValue)if(l.currentValue&&"disableCertPemCredentials"===i)"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((function(){n.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}},r.prototype.ngOnDestroy=function(){this.subscriptions.forEach((function(e){return e.unsubscribe()}))},r.prototype.writeValue=function(e){s.isDefinedAndNotNull(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))},r.prototype.setDisabledState=function(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())},r.prototype.updateView=function(){var e=this.credentialsConfigFormGroup.value,t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.validate=function(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}},r.prototype.credentialsTypeChanged=function(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()},r.prototype.updateValidators=function(e){void 0===e&&(e=!1);var t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([i.Validators.required]),this.credentialsConfigFormGroup.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(i.Validators.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})},r.prototype.requiredFilesSelected=function(e,t){return void 0===t&&(t=null),function(r){return t||(t=[Object.keys(r.controls)]),(null==r?void 0:r.controls)&&t.some((function(t){return t.every((function(t){return!e(r.controls[t])}))}))?null:{notAllRequiredFilesSelected:!0}}},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",Object)],r.prototype,"disableCertPemCredentials",void 0),r=n=h([t.Component({selector:"tb-credentials-config",template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.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
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n
\n
\n
\n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),me=function(){function e(e){this.sanitizer=e}return e.prototype.transform=function(e){return this.sanitizer.bypassSecurityTrustHtml(e)},e.ctorParameters=function(){return[{type:g.DomSanitizer}]},e=h([t.Pipe({name:"safeHtml"}),C("design:paramtypes",[g.DomSanitizer])],e)}(),ue=function(){function e(){}return e=h([t.NgModule({declarations:[ae,oe,ie,le,se,me],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ae,oe,ie,le,se,me]})],e)}(),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.prepareOutputConfig=function(e){return e.customerNamePattern=e.customerNamePattern.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("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 b(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=h([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'}),C("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.sqsQueueType=j,n.sqsQueueTypes=Object.keys(j),n.sqsQueueTypeTranslationsMap=_,n}return b(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=h([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'}),C("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}return b(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=h([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'}),C("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.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Z,n.ToByteStandartCharsetTypeTranslationMap=X,n}return b(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=h([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 \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'}),C("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}return b(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:[e?e.credentials:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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'}),C("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.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return b(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=h([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'}),C("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.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(Q),n}return b(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,[]],credentials:[e?e.credentials: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}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\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
\n'}),C("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.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return b(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=h([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'}),C("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.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return b(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=h([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),C("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.allAzureIotHubCredentialsTypes=J,n.azureIotHubCredentialsTypeTranslationsMap=Y,n}return b(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=h([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}"]}),C("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}return b(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=h([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'}),C("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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(){function e(){}return e=h([t.NgModule({declarations:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,ue],exports:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te]})],e)}(),Se=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 b(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=h([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}"]}),C("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.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=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=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),C("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.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 b(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=h([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}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ae=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Le=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=v(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 b(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}]},h([t.ViewChild("alarmStatusInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=h([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'}),C("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(){function e(){}return e=h([t.NgModule({declarations:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le],imports:[r.CommonModule,a.SharedModule,ue],exports:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le]})],e)}(),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=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=z,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=v(Object.keys(B)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(B[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 b(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(z.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(z.get(B[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}]},h([t.ViewChild("detailsInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=h([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}"]}),C("design:paramtypes",[o.Store,n.TranslateService,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=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(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=h([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
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("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.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(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=h([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
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("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 b(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=h([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=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=H,n.fetchModes=Object.keys(H),n.samplingOrders=Object.keys(U),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(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===H.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=h([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}"]}),C("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}return b(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=h([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'}),C("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}return b(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=h([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("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.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(r,e),r.prototype.configForm=function(){return this.calculateDeltaConfigForm},r.prototype.onConfigurationSet=function(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e?e.inputValueKey:null,[i.Validators.required]],outputValueKey:[e?e.outputValueKey:null,[i.Validators.required]],useCache:[e?e.useCache:null,[]],addPeriodBetweenMsgs:[!!e&&e.addPeriodBetweenMsgs,[]],periodValueKey:[e?e.periodValueKey:null,[]],round:[e?e.round:null,[i.Validators.min(0),i.Validators.max(15)]],tellFailureIfDeltaIsNegative:[e?e.tellFailureIfDeltaIsNegative:null,[]]})},r.prototype.updateValidators=function(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([i.Validators.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})},r.prototype.validatorTriggers=function(){return["addPeriodBetweenMsgs"]},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-calculate-delta-config",template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(){function e(){}return e=h([t.NgModule({declarations:[Me,we,Re,De,Oe,Ke,Be,Ge,He],imports:[r.CommonModule,a.SharedModule,ue],exports:[Me,we,Re,De,Oe,Ke,Be,Ge,He]})],e)}(),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=F,n.originatorSources=Object.keys(F),n.originatorSourceTranslationMap=w,n}return b(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===F.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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ze=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),_e=function(){function e(){}return e=h([t.NgModule({declarations:[je,ze,Qe],imports:[r.CommonModule,a.SharedModule,ue],exports:[je,ze,Qe]})],e)}(),$e=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-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","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-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","shared-attributes":"Shared attributes","server-attributes":"Server attributes","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","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-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 period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","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","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',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",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","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"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-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","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":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client 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 interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","use-dynamically-change-the-severity-of-alar":"Use dynamically change the severity of alarm","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","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","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"alarm-severity-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)'},"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=h([t.NgModule({declarations:[x],imports:[r.CommonModule,a.SharedModule],exports:[qe,Pe,Ue,_e,x]}),C("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=$e,e.ɵa=x,e.ɵb=qe,e.ɵba=ve,e.ɵbb=Fe,e.ɵbc=xe,e.ɵbd=Te,e.ɵbe=ue,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=le,e.ɵbj=se,e.ɵbk=me,e.ɵbl=Pe,e.ɵbm=Se,e.ɵbn=Ie,e.ɵbo=ke,e.ɵbp=Ne,e.ɵbq=Ve,e.ɵbr=Ee,e.ɵbs=Ae,e.ɵbt=Le,e.ɵbu=Ue,e.ɵbv=Me,e.ɵbw=we,e.ɵbx=Re,e.ɵby=De,e.ɵbz=Oe,e.ɵc=T,e.ɵca=Ke,e.ɵcb=Be,e.ɵcc=Ge,e.ɵcd=He,e.ɵce=_e,e.ɵcf=je,e.ɵcg=ze,e.ɵch=Qe,e.ɵd=q,e.ɵe=S,e.ɵf=I,e.ɵg=k,e.ɵh=N,e.ɵi=V,e.ɵj=E,e.ɵk=A,e.ɵl=L,e.ɵm=P,e.ɵn=ee,e.ɵo=te,e.ɵp=re,e.ɵq=ne,e.ɵr=de,e.ɵs=pe,e.ɵt=ce,e.ɵu=fe,e.ɵv=ge,e.ɵw=ye,e.ɵx=be,e.ɵy=he,e.ɵz=Ce,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var y=function(e,t){return(y=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 b(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");function r(){this.constructor=e}y(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function h(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 C(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function v(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 F,x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-node-empty-config",template:"
"}),C("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.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return b(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=h([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'}),C("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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),I=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.prepareOutputConfig=function(e){return e.customerNamePattern=e.customerNamePattern.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),N=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=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 b(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){var t=this;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,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((function(e){e?t.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):t.createAlarmConfigForm.get("severity").patchValue(t.alarmSeverities[0],{emitEvent:!1})}))},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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.use-dynamically-change-the-severity-of-alar\' | translate }}\n \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 {{ \'tb.rulenode.alarm-severity\' | translate }}\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \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 tb.rulenode.relation-types-list-hint\n \n
\n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],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.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return b(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,i.Validators.pattern(/.*\S.*/)]):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,i.Validators.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.prototype.prepareOutputConfig=function(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("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}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=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 b(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,i.Validators.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.prototype.prepareOutputConfig=function(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.entity-cache-expiration-hint\n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),P=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([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'}),C("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"}(F||(F={}));var M,w=new Map([[F.CUSTOMER,"tb.rulenode.originator-customer"],[F.TENANT,"tb.rulenode.originator-tenant"],[F.RELATED,"tb.rulenode.originator-related"],[F.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,D=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,K=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 B,G=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"}(B||(B={}));var H,U,j,z=new Map([[B.TITLE,"tb.rulenode.entity-details-title"],[B.COUNTRY,"tb.rulenode.entity-details-country"],[B.STATE,"tb.rulenode.entity-details-state"],[B.ZIP,"tb.rulenode.entity-details-zip"],[B.ADDRESS,"tb.rulenode.entity-details-address"],[B.ADDRESS2,"tb.rulenode.entity-details-address2"],[B.PHONE,"tb.rulenode.entity-details-phone"],[B.EMAIL,"tb.rulenode.entity-details-email"],[B.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(H||(H={})),function(e){e.ASC="ASC",e.DESC="DESC"}(U||(U={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(j||(j={}));var Q,_=new Map([[j.STANDARD,"tb.rulenode.sqs-queue-standard"],[j.FIFO,"tb.rulenode.sqs-queue-fifo"]]),$=["anonymous","basic","cert.PEM"],W=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),J=["sas","cert.PEM"],Y=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"}(Q||(Q={}));var Z=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],X=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"]]),ee=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=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=K,n}return b(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=h([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'}),C("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 b(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=h([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'}),C("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 b(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=h([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.prototype.prepareOutputConfig=function(e){return e.tableName=e.tableName.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.custom-table-hint\n \n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ae=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 b(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=v(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",String)],r.prototype,"requiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"keyRequiredText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valText",void 0),h([t.Input(),C("design:type",String)],r.prototype,"valRequiredText",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=h([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\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}"]}),C("design:paramtypes",[o.Store,n.TranslateService,t.Injector,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.entityType=a.EntityType,n.propagateChange=null,n}var n;return b(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([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}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=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 b(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}]},h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=h([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}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),le=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=v(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 b(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}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",String)],r.prototype,"label",void 0),h([t.Input(),C("design:type",Object)],r.prototype,"placeholder",void 0),h([t.Input(),C("design:type",Boolean)],r.prototype,"disabled",void 0),h([t.ViewChild("chipList",{static:!1}),C("design:type",d.MatChipList)],r.prototype,"chipList",void 0),h([t.ViewChild("messageTypeAutocomplete",{static:!1}),C("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),h([t.ViewChild("messageTypeInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=h([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}]}),C("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.subscriptions=[],n.disableCertPemCredentials=!1,n.allCredentialsTypes=$,n.credentialsTypeTranslationsMap=W,n.propagateChange=null,n}var n;return b(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.credentialsConfigFormGroup=this.fb.group({type:[null,[i.Validators.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(f.distinctUntilChanged()).subscribe((function(){e.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((function(){e.credentialsTypeChanged()})))},r.prototype.ngOnChanges=function(e){var t,r,n=this;try{for(var a=v(Object.keys(e)),o=a.next();!o.done;o=a.next()){var i=o.value,l=e[i];if(!l.firstChange&&l.currentValue!==l.previousValue)if(l.currentValue&&"disableCertPemCredentials"===i)"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((function(){n.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}catch(e){t={error:e}}finally{try{o&&!o.done&&(r=a.return)&&r.call(a)}finally{if(t)throw t.error}}},r.prototype.ngOnDestroy=function(){this.subscriptions.forEach((function(e){return e.unsubscribe()}))},r.prototype.writeValue=function(e){s.isDefinedAndNotNull(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))},r.prototype.setDisabledState=function(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())},r.prototype.updateView=function(){var e=this.credentialsConfigFormGroup.value,t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.validate=function(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}},r.prototype.credentialsTypeChanged=function(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()},r.prototype.updateValidators=function(e){void 0===e&&(e=!1);var t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([i.Validators.required]),this.credentialsConfigFormGroup.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(i.Validators.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})},r.prototype.requiredFilesSelected=function(e,t){return void 0===t&&(t=null),function(r){return t||(t=[Object.keys(r.controls)]),(null==r?void 0:r.controls)&&t.some((function(t){return t.every((function(t){return!e(r.controls[t])}))}))?null:{notAllRequiredFilesSelected:!0}}},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},h([t.Input(),C("design:type",Boolean),C("design:paramtypes",[Boolean])],r.prototype,"required",null),h([t.Input(),C("design:type",Object)],r.prototype,"disableCertPemCredentials",void 0),r=n=h([t.Component({selector:"tb-credentials-config",template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.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
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n
\n
\n
\n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),me=function(){function e(e){this.sanitizer=e}return e.prototype.transform=function(e){return this.sanitizer.bypassSecurityTrustHtml(e)},e.ctorParameters=function(){return[{type:g.DomSanitizer}]},e=h([t.Pipe({name:"safeHtml"}),C("design:paramtypes",[g.DomSanitizer])],e)}(),ue=function(){function e(){}return e=h([t.NgModule({declarations:[ae,oe,ie,le,se,me],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule],exports:[ae,oe,ie,le,se,me]})],e)}(),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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,i.Validators.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.prepareOutputConfig=function(e){return e.customerNamePattern=e.customerNamePattern.trim(),e},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.customer-cache-expiration-hint\n \n
\n'}),C("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 b(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=h([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'}),C("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.sqsQueueType=j,n.sqsQueueTypes=Object.keys(j),n.sqsQueueTypeTranslationsMap=_,n}return b(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=h([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'}),C("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}return b(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=h([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'}),C("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.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Z,n.ToByteStandartCharsetTypeTranslationMap=X,n}return b(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=h([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 \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'}),C("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}return b(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:[e?e.credentials:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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'}),C("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.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return b(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=h([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'}),C("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.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(Q),n}return b(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,[]],credentials:[e?e.credentials: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}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([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 tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\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
\n'}),C("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.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return b(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=h([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'}),C("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.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return b(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=h([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),C("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.allAzureIotHubCredentialsTypes=J,n.azureIotHubCredentialsTypeTranslationsMap=Y,n}return b(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=h([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}"]}),C("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}return b(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=h([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'}),C("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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(){function e(){}return e=h([t.NgModule({declarations:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te],imports:[r.CommonModule,a.SharedModule,l.HomeComponentsModule,ue],exports:[T,q,S,I,k,N,V,E,A,L,P,ee,te,re,ne,de,pe,ce,fe,ge,ye,be,he,Ce,ve,Fe,xe,Te]})],e)}(),Se=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 b(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=h([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}"]}),C("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.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ke=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=D,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=G,n}return b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),C("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.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 b(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=h([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}"]}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ee=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ae=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Le=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=v(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 b(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}]},h([t.ViewChild("alarmStatusInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=h([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'}),C("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(){function e(){}return e=h([t.NgModule({declarations:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le],imports:[r.CommonModule,a.SharedModule,ue],exports:[Se,Ie,ke,Ne,Ve,Ee,Ae,Le]})],e)}(),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return b(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=h([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=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=z,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=v(Object.keys(B)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(B[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 b(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(z.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(z.get(B[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}]},h([t.ViewChild("detailsInput",{static:!1}),C("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=h([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}"]}),C("design:paramtypes",[o.Store,n.TranslateService,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=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(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=h([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
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("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.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(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=h([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
tb.rulenode.get-latest-value-with-ts-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),C("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 b(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=h([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ke=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=H,n.fetchModes=Object.keys(H),n.samplingOrders=Object.keys(U),n.timeUnits=Object.values(R),n.timeUnitsTranslationMap=K,n}return b(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===H.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=h([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}"]}),C("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}return b(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=h([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'}),C("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}return b(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=h([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),C("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.separatorKeysCodes=[m.ENTER,m.COMMA,m.SEMICOLON],n}return b(r,e),r.prototype.configForm=function(){return this.calculateDeltaConfigForm},r.prototype.onConfigurationSet=function(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e?e.inputValueKey:null,[i.Validators.required]],outputValueKey:[e?e.outputValueKey:null,[i.Validators.required]],useCache:[e?e.useCache:null,[]],addPeriodBetweenMsgs:[!!e&&e.addPeriodBetweenMsgs,[]],periodValueKey:[e?e.periodValueKey:null,[]],round:[e?e.round:null,[i.Validators.min(0),i.Validators.max(15)]],tellFailureIfDeltaIsNegative:[e?e.tellFailureIfDeltaIsNegative:null,[]]})},r.prototype.updateValidators=function(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([i.Validators.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})},r.prototype.validatorTriggers=function(){return["addPeriodBetweenMsgs"]},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-enrichment-node-calculate-delta-config",template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ue=function(){function e(){}return e=h([t.NgModule({declarations:[Me,we,Re,De,Oe,Ke,Be,Ge,He],imports:[r.CommonModule,a.SharedModule,ue],exports:[Me,we,Re,De,Oe,Ke,Be,Ge,He]})],e)}(),je=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=F,n.originatorSources=Object.keys(F),n.originatorSourceTranslationMap=w,n}return b(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===F.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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ze=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 b(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}]},h([t.ViewChild("jsFuncComponent",{static:!0}),C("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=h([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder,s.NodeScriptTestService,n.TranslateService])],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 b(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=h([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'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),_e=function(){function e(){}return e=h([t.NgModule({declarations:[je,ze,Qe],imports:[r.CommonModule,a.SharedModule,ue],exports:[je,ze,Qe]})],e)}(),$e=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-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","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-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","shared-attributes":"Shared attributes","server-attributes":"Server attributes","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","timeseries-key":"Timeseries key","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-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 period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","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","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',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",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","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"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-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","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":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client 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 interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","use-dynamically-change-the-severity-of-alar":"Use dynamically change the severity of alarm","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","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 ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","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","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"alarm-severity-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)'},"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=h([t.NgModule({declarations:[x],imports:[r.CommonModule,a.SharedModule],exports:[qe,Pe,Ue,_e,x]}),C("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=$e,e.ɵa=x,e.ɵb=qe,e.ɵba=ve,e.ɵbb=Fe,e.ɵbc=xe,e.ɵbd=Te,e.ɵbe=ue,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=le,e.ɵbj=se,e.ɵbk=me,e.ɵbl=Pe,e.ɵbm=Se,e.ɵbn=Ie,e.ɵbo=ke,e.ɵbp=Ne,e.ɵbq=Ve,e.ɵbr=Ee,e.ɵbs=Ae,e.ɵbt=Le,e.ɵbu=Ue,e.ɵbv=Me,e.ɵbw=we,e.ɵbx=Re,e.ɵby=De,e.ɵbz=Oe,e.ɵc=T,e.ɵca=Ke,e.ɵcb=Be,e.ɵcc=Ge,e.ɵcd=He,e.ɵce=_e,e.ɵcf=je,e.ɵcg=ze,e.ɵch=Qe,e.ɵd=q,e.ɵe=S,e.ɵf=I,e.ɵg=k,e.ɵh=N,e.ɵi=V,e.ɵj=E,e.ɵk=A,e.ɵl=L,e.ɵm=P,e.ɵn=ee,e.ɵo=te,e.ɵp=re,e.ɵq=ne,e.ɵr=de,e.ɵs=pe,e.ɵt=ce,e.ɵu=fe,e.ɵv=ge,e.ɵw=ye,e.ɵx=be,e.ɵy=he,e.ɵz=Ce,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file From 9b01f67d8148e9602d6f3c62297484475764809b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 22 Apr 2021 12:21:18 +0300 Subject: [PATCH 049/127] Update firmware only when it is really changed --- .../server/controller/DeviceController.java | 19 ++++------- .../firmware/DefaultFirmwareStateService.java | 32 +++++++++++++------ .../firmware/FirmwareStateService.java | 2 +- 3 files changed, 29 insertions(+), 24 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 508ca124a3..433d4dd742 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -126,16 +126,11 @@ public class DeviceController extends BaseController { checkEntity(device.getId(), device, Resource.DEVICE); boolean created = device.getId() == null; - - boolean isFirmwareChanged = false; - - if (created) { - isFirmwareChanged = true; + Device oldDevice; + if (!created) { + oldDevice = deviceService.findDeviceById(getTenantId(), device.getId()); } else { - Device oldDevice = deviceService.findDeviceById(getTenantId(), device.getId()); - if (!Objects.equals(device.getFirmwareId(), oldDevice.getFirmwareId()) || !oldDevice.getDeviceProfileId().equals(device.getDeviceProfileId())) { - isFirmwareChanged = true; - } + oldDevice = null; } Device savedDevice = checkNotNull(deviceService.saveDeviceWithAccessToken(device, accessToken)); @@ -145,7 +140,7 @@ public class DeviceController extends BaseController { savedDevice.getId(), savedDevice.getName(), savedDevice.getType()), null); tbClusterService.onEntityStateChange(savedDevice.getTenantId(), savedDevice.getId(), created ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); - if (device.getId() != null) { + if (!created) { sendEntityNotificationMsg(savedDevice.getTenantId(), savedDevice.getId(), EdgeEventActionType.UPDATED); } @@ -159,9 +154,7 @@ public class DeviceController extends BaseController { deviceStateService.onDeviceUpdated(savedDevice); } - if (isFirmwareChanged) { - firmwareStateService.update(savedDevice, created); - } + firmwareStateService.update(savedDevice, oldDevice); return savedDevice; } catch ( diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index 3e03dd99e0..0c0b4d15e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -43,6 +43,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM; @@ -69,19 +70,30 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } @Override - public void update(Device device, boolean created) { - FirmwareId firmwareId = device.getFirmwareId(); - if (firmwareId == null) { - DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); - firmwareId = deviceProfile.getFirmwareId(); + public void update(Device device, Device oldDevice) { + FirmwareId newFirmwareId = device.getFirmwareId(); + if (newFirmwareId == null) { + DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + newFirmwareId = newDeviceProfile.getFirmwareId(); } - - if (firmwareId == null) { - if (!created) { + if (oldDevice != null) { + if (newFirmwareId != null) { + FirmwareId oldFirmwareId = oldDevice.getFirmwareId(); + if (oldFirmwareId == null) { + DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId()); + oldFirmwareId = oldDeviceProfile.getFirmwareId(); + } + if (!newFirmwareId.equals(oldFirmwareId)) { + // Device was updated and new firmware is different from previous firmware. + update(device, firmwareService.findFirmwareById(device.getTenantId(), newFirmwareId), System.currentTimeMillis()); + } + } else { + // Device was updated and new firmware is not set. remove(device); } - } else { - update(device, firmwareService.findFirmwareById(device.getTenantId(), firmwareId), System.currentTimeMillis()); + } else if (newFirmwareId != null) { + // Device was created and firmware is defined. + update(device, firmwareService.findFirmwareById(device.getTenantId(), newFirmwareId), System.currentTimeMillis()); } } diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java index 21dc7d8696..3f84e5d0fb 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java @@ -20,7 +20,7 @@ import org.thingsboard.server.common.data.DeviceProfile; public interface FirmwareStateService { - void update(Device device, boolean created); + void update(Device device, Device oldDevice); void update(DeviceProfile deviceProfile); From 609024a603c72e92658e53d9fa60ccc98bf7e0aa Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 22 Apr 2021 13:03:10 +0300 Subject: [PATCH 050/127] added redis configs to the transports yml --- .../firmware/CaffeineFirmwareCacheReader.java | 2 +- .../firmware/CaffeineFirmwareCacheWriter.java | 2 +- .../firmware/RedisFirmwareCacheWriter.java | 2 +- .../ThingsboardCoapTransportApplication.java | 2 +- .../src/main/resources/tb-coap-transport.yml | 44 +++++++++++++++++++ .../ThingsboardHttpTransportApplication.java | 2 +- .../src/main/resources/tb-http-transport.yml | 44 +++++++++++++++++++ .../src/main/resources/tb-lwm2m-transport.yml | 35 +-------------- .../ThingsboardMqttTransportApplication.java | 2 +- .../src/main/resources/tb-mqtt-transport.yml | 44 +++++++++++++++++++ 10 files changed, 139 insertions(+), 40 deletions(-) diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java index f7e05f818c..ac53027628 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Service; import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; @Service -@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='caffeine')") +@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='null')") public class CaffeineFirmwareCacheReader implements FirmwareCacheReader { private final CacheManager cacheManager; diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java index e543739382..bba4c827bb 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Service; import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; @Service -@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='core') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='caffeine')") +@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='null')") public class CaffeineFirmwareCacheWriter implements FirmwareCacheWriter { private final CacheManager cacheManager; diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java index 6ba3ee91f6..7bbb6045e9 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java @@ -21,7 +21,7 @@ import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; @Service -@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='core') && '${cache.type:null}'=='redis'") +@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${cache.type:null}'=='redis'") public class RedisFirmwareCacheWriter extends AbstractRedisFirmwareCache implements FirmwareCacheWriter { public RedisFirmwareCacheWriter(RedisConnectionFactory redisConnectionFactory) { diff --git a/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java b/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java index 010a774e0e..dac2306b0c 100644 --- a/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java +++ b/transport/coap/src/main/java/org/thingsboard/server/coap/ThingsboardCoapTransportApplication.java @@ -26,7 +26,7 @@ import java.util.Arrays; @SpringBootConfiguration @EnableAsync @EnableScheduling -@ComponentScan({"org.thingsboard.server.coap", "org.thingsboard.server.common", "org.thingsboard.server.coapserver", "org.thingsboard.server.transport.coap", "org.thingsboard.server.queue"}) +@ComponentScan({"org.thingsboard.server.coap", "org.thingsboard.server.common", "org.thingsboard.server.coapserver", "org.thingsboard.server.transport.coap", "org.thingsboard.server.queue", "org.thingsboard.server.cache"}) public class ThingsboardCoapTransportApplication { private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 4d5bbf2c6f..18ef3e349c 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -40,6 +40,50 @@ zk: # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" +cache: + # caffeine or redis + type: "${CACHE_TYPE:redis}" + +redis: + # standalone or cluster + connection: + type: "${REDIS_CONNECTION_TYPE:standalone}" + standalone: + host: "${REDIS_HOST:localhost}" + port: "${REDIS_PORT:6379}" + useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}" + # this value may be used only if you used not default ClientConfig + clientName: "${REDIS_CLIENT_NAME:standalone}" + # this value may be used only if you used not default ClientConfig + connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}" + # this value may be used only if you used not default ClientConfig + readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" + # this value may be used only if you used not default ClientConfig + usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}" + cluster: + # Comma-separated list of "host:port" pairs to bootstrap from. + nodes: "${REDIS_NODES:}" + # Maximum number of redirects to follow when executing commands across the cluster. + max-redirects: "${REDIS_MAX_REDIRECTS:12}" + useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}" + # db index + db: "${REDIS_DB:0}" + # db password + password: "${REDIS_PASSWORD:}" + # pool config + pool_config: + maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" + maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}" + minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}" + testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}" + testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}" + testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}" + minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}" + evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}" + maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}" + numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}" + blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" + # COAP server parameters transport: coap: diff --git a/transport/http/src/main/java/org/thingsboard/server/http/ThingsboardHttpTransportApplication.java b/transport/http/src/main/java/org/thingsboard/server/http/ThingsboardHttpTransportApplication.java index 88e5eab73d..0c0b772f37 100644 --- a/transport/http/src/main/java/org/thingsboard/server/http/ThingsboardHttpTransportApplication.java +++ b/transport/http/src/main/java/org/thingsboard/server/http/ThingsboardHttpTransportApplication.java @@ -24,7 +24,7 @@ import java.util.Arrays; @SpringBootApplication @EnableAsync -@ComponentScan({"org.thingsboard.server.http", "org.thingsboard.server.common", "org.thingsboard.server.transport.http", "org.thingsboard.server.queue"}) +@ComponentScan({"org.thingsboard.server.http", "org.thingsboard.server.common", "org.thingsboard.server.transport.http", "org.thingsboard.server.queue", "org.thingsboard.server.cache"}) public class ThingsboardHttpTransportApplication { private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 7fc93371f6..b34f8940bc 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -35,6 +35,50 @@ zk: # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" +cache: + # caffeine or redis + type: "${CACHE_TYPE:redis}" + +redis: + # standalone or cluster + connection: + type: "${REDIS_CONNECTION_TYPE:standalone}" + standalone: + host: "${REDIS_HOST:localhost}" + port: "${REDIS_PORT:6379}" + useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}" + # this value may be used only if you used not default ClientConfig + clientName: "${REDIS_CLIENT_NAME:standalone}" + # this value may be used only if you used not default ClientConfig + connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}" + # this value may be used only if you used not default ClientConfig + readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" + # this value may be used only if you used not default ClientConfig + usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}" + cluster: + # Comma-separated list of "host:port" pairs to bootstrap from. + nodes: "${REDIS_NODES:}" + # Maximum number of redirects to follow when executing commands across the cluster. + max-redirects: "${REDIS_MAX_REDIRECTS:12}" + useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}" + # db index + db: "${REDIS_DB:0}" + # db password + password: "${REDIS_PASSWORD:}" + # pool config + pool_config: + maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" + maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}" + minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}" + testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}" + testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}" + testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}" + minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}" + evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}" + maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}" + numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}" + blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" + # HTTP server parameters transport: http: diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 922aed5450..25fee65cc0 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -42,40 +42,7 @@ zk: cache: # caffeine or redis - type: "${CACHE_TYPE:caffeine}" - -caffeine: - specs: - relations: - timeToLiveInMinutes: 1440 - maxSize: 0 - deviceCredentials: - timeToLiveInMinutes: 1440 - maxSize: 0 - devices: - timeToLiveInMinutes: 1440 - maxSize: 0 - sessions: - timeToLiveInMinutes: 1440 - maxSize: 0 - assets: - timeToLiveInMinutes: 1440 - maxSize: 0 - entityViews: - timeToLiveInMinutes: 1440 - maxSize: 0 - claimDevices: - timeToLiveInMinutes: 1 - maxSize: 0 - securitySettings: - timeToLiveInMinutes: 1440 - maxSize: 0 - tenantProfiles: - timeToLiveInMinutes: 1440 - maxSize: 0 - deviceProfiles: - timeToLiveInMinutes: 1440 - maxSize: 0 + type: "${CACHE_TYPE:redis}" redis: # standalone or cluster diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java b/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java index 09250af629..51e4b2c181 100644 --- a/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java +++ b/transport/mqtt/src/main/java/org/thingsboard/server/mqtt/ThingsboardMqttTransportApplication.java @@ -26,7 +26,7 @@ import java.util.Arrays; @SpringBootConfiguration @EnableAsync @EnableScheduling -@ComponentScan({"org.thingsboard.server.mqtt", "org.thingsboard.server.common", "org.thingsboard.server.transport.mqtt", "org.thingsboard.server.queue"}) +@ComponentScan({"org.thingsboard.server.mqtt", "org.thingsboard.server.common", "org.thingsboard.server.transport.mqtt", "org.thingsboard.server.queue", "org.thingsboard.server.cache"}) public class ThingsboardMqttTransportApplication { private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index d641463177..ed30613043 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -40,6 +40,50 @@ zk: # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" +cache: + # caffeine or redis + type: "${CACHE_TYPE:redis}" + +redis: + # standalone or cluster + connection: + type: "${REDIS_CONNECTION_TYPE:standalone}" + standalone: + host: "${REDIS_HOST:localhost}" + port: "${REDIS_PORT:6379}" + useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}" + # this value may be used only if you used not default ClientConfig + clientName: "${REDIS_CLIENT_NAME:standalone}" + # this value may be used only if you used not default ClientConfig + connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}" + # this value may be used only if you used not default ClientConfig + readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" + # this value may be used only if you used not default ClientConfig + usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}" + cluster: + # Comma-separated list of "host:port" pairs to bootstrap from. + nodes: "${REDIS_NODES:}" + # Maximum number of redirects to follow when executing commands across the cluster. + max-redirects: "${REDIS_MAX_REDIRECTS:12}" + useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}" + # db index + db: "${REDIS_DB:0}" + # db password + password: "${REDIS_PASSWORD:}" + # pool config + pool_config: + maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" + maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}" + minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}" + testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}" + testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}" + testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}" + minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}" + evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}" + maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}" + numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}" + blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" + # MQTT server parameters transport: mqtt: From f89b30777e6bad9c96e0cfae8010002b04b00fc1 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Fri, 16 Apr 2021 13:10:38 +0300 Subject: [PATCH 051/127] Initial implementation for per-customer api usage stats --- .../RuleNodeActorMessageProcessor.java | 4 +- .../server/controller/BaseController.java | 2 +- .../server/controller/DeviceController.java | 2 +- .../server/controller/RpcController.java | 2 +- .../controller/TelemetryController.java | 3 +- .../service/apiusage/BaseApiUsageState.java | 150 ++++++++++++++ .../apiusage/CustomerApiUsageState.java | 24 +++ .../DefaultTbApiUsageStateService.java | 187 +++++++++++++----- .../service/apiusage/TenantApiUsageState.java | 137 +------------ .../service/edge/rpc/EdgeGrpcSession.java | 2 +- .../edge/rpc/processor/DeviceProcessor.java | 2 +- .../rpc/processor/TelemetryProcessor.java | 20 +- .../install/SqlDatabaseUpgradeService.java | 2 +- .../service/mail/DefaultMailService.java | 5 +- .../queue/DefaultTbClusterService.java | 9 +- .../service/queue/TbClusterService.java | 3 + .../rpc/DefaultTbCoreDeviceRpcService.java | 9 +- .../service/rpc/TbCoreDeviceRpcService.java | 7 +- .../script/AbstractJsInvokeService.java | 5 +- .../service/script/JsInvokeService.java | 4 +- .../script/RuleNodeJsScriptEngine.java | 4 +- .../server/service/sms/DefaultSmsService.java | 5 +- .../state/DefaultDeviceStateService.java | 3 +- .../server/service/state/DeviceStateData.java | 2 + .../DefaultTelemetrySubscriptionService.java | 10 +- .../transport/DefaultTransportApiService.java | 5 +- .../controller/BaseWebsocketApiTest.java | 4 +- .../dao/usagerecord/ApiUsageStateService.java | 5 +- .../thingsboard/server/common/msg/TbMsg.java | 2 + .../usagestats/DefaultTbApiUsageClient.java | 106 ++++++---- .../queue/usagestats/TbApiUsageClient.java | 5 +- common/queue/src/main/proto/queue.proto | 6 + .../transport/auth/SessionInfoCreator.java | 2 + .../transport/auth/TransportDeviceInfo.java | 2 + .../service/DefaultTransportService.java | 43 ++-- .../usagerecord/ApiUsageStateRepository.java | 2 + .../sql/usagerecord/JpaApiUsageStateDao.java | 6 + .../server/dao/tenant/TenantServiceImpl.java | 2 +- .../dao/usagerecord/ApiUsageStateDao.java | 3 + .../usagerecord/ApiUsageStateServiceImpl.java | 18 +- .../rule/engine/api/MailService.java | 3 +- .../api/RuleEngineTelemetryService.java | 3 +- .../rule/engine/api/SmsService.java | 3 +- .../rule/engine/mail/TbSendEmailNode.java | 7 +- .../rule/engine/sms/TbSendSmsNode.java | 2 +- .../engine/telemetry/TbMsgTimeseriesNode.java | 3 +- 46 files changed, 541 insertions(+), 294 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java index 573310f7cd..9fd33a91d7 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java @@ -106,7 +106,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse)); + deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse), currentUser); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index daca2d1a5d..d311cdbfed 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -48,6 +48,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -449,7 +450,7 @@ public class TelemetryController extends BaseController { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); tenantTtl = TimeUnit.DAYS.toSeconds(((DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration()).getDefaultStorageTtlDays()); } - tsSubService.saveAndNotify(tenantId, entityId, entries, tenantTtl, new FutureCallback() { + tsSubService.saveAndNotify(tenantId, user.getCustomerId(), entityId, entries, tenantTtl, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { logTelemetryUpdated(user, entityId, entries, null); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java new file mode 100644 index 0000000000..37a2d986ac --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java @@ -0,0 +1,150 @@ +package org.thingsboard.server.service.apiusage; + +import lombok.Getter; +import org.springframework.data.util.Pair; +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.ApiUsageStateValue; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.tools.SchedulerUtils; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public abstract class BaseApiUsageState { + private final Map currentCycleValues = new ConcurrentHashMap<>(); + private final Map currentHourValues = new ConcurrentHashMap<>(); + + @Getter + private final ApiUsageState apiUsageState; + @Getter + private volatile long currentCycleTs; + @Getter + private volatile long nextCycleTs; + @Getter + private volatile long currentHourTs; + + public BaseApiUsageState(ApiUsageState apiUsageState) { + this.apiUsageState = apiUsageState; + this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); + this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); + this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); + } + + public void put(ApiUsageRecordKey key, Long value) { + currentCycleValues.put(key, value); + } + + public void putHourly(ApiUsageRecordKey key, Long value) { + currentHourValues.put(key, value); + } + + public long add(ApiUsageRecordKey key, long value) { + long result = currentCycleValues.getOrDefault(key, 0L) + value; + currentCycleValues.put(key, result); + return result; + } + + public long get(ApiUsageRecordKey key) { + return currentCycleValues.getOrDefault(key, 0L); + } + + public long addToHourly(ApiUsageRecordKey key, long value) { + long result = currentHourValues.getOrDefault(key, 0L) + value; + currentHourValues.put(key, result); + return result; + } + + public void setHour(long currentHourTs) { + this.currentHourTs = currentHourTs; + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + currentHourValues.put(key, 0L); + } + } + + public void setCycles(long currentCycleTs, long nextCycleTs) { + this.currentCycleTs = currentCycleTs; + this.nextCycleTs = nextCycleTs; + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + currentCycleValues.put(key, 0L); + } + } + + public ApiUsageStateValue getFeatureValue(ApiFeature feature) { + switch (feature) { + case TRANSPORT: + return apiUsageState.getTransportState(); + case RE: + return apiUsageState.getReExecState(); + case DB: + return apiUsageState.getDbStorageState(); + case JS: + return apiUsageState.getJsExecState(); + case EMAIL: + return apiUsageState.getEmailExecState(); + case SMS: + return apiUsageState.getSmsExecState(); + default: + return ApiUsageStateValue.ENABLED; + } + } + + public boolean setFeatureValue(ApiFeature feature, ApiUsageStateValue value) { + ApiUsageStateValue currentValue = getFeatureValue(feature); + switch (feature) { + case TRANSPORT: + apiUsageState.setTransportState(value); + break; + case RE: + apiUsageState.setReExecState(value); + break; + case DB: + apiUsageState.setDbStorageState(value); + break; + case JS: + apiUsageState.setJsExecState(value); + break; + case EMAIL: + apiUsageState.setEmailExecState(value); + break; + case SMS: + apiUsageState.setSmsExecState(value); + break; + } + return !currentValue.equals(value); + } + + public Map checkStateUpdatedDueToThresholds() { + return checkStateUpdatedDueToThreshold(new HashSet<>(Arrays.asList(ApiFeature.values()))); + } + + public Map checkStateUpdatedDueToThreshold(Set features) { + Map result = new HashMap<>(); + for (ApiFeature feature : features) { + Pair tmp = checkStateUpdatedDueToThreshold(feature); + if (tmp != null) { + result.put(tmp.getFirst(), tmp.getSecond()); + } + } + return result; + } + + public abstract Pair checkStateUpdatedDueToThreshold(ApiFeature feature); + + public abstract EntityType getEntityType(); + + public TenantId getTenantId() { + return getApiUsageState().getTenantId(); + } + + public EntityId getEntityId() { + return getApiUsageState().getEntityId(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java new file mode 100644 index 0000000000..76b3e63fc6 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java @@ -0,0 +1,24 @@ +package org.thingsboard.server.service.apiusage; + +import org.springframework.data.util.Pair; +import org.thingsboard.server.common.data.ApiFeature; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; +import org.thingsboard.server.common.data.EntityType; + +public class CustomerApiUsageState extends BaseApiUsageState { + public CustomerApiUsageState(ApiUsageState apiUsageState) { + super(apiUsageState); + } + + @Override + public Pair checkStateUpdatedDueToThreshold(ApiFeature feature) { + ApiUsageStateValue featureValue = ApiUsageStateValue.ENABLED; + return setFeatureValue(feature, featureValue) ? Pair.of(feature, featureValue) : null; + } + + @Override + public EntityType getEntityType() { + return EntityType.CUSTOMER; + } +} 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 c2549803e5..fb77a65255 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 @@ -29,10 +29,13 @@ 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.EntityType; 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.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; @@ -66,6 +69,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -106,9 +110,9 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener myTenantStates = new ConcurrentHashMap<>(); + private final Map myUsageStates = new ConcurrentHashMap<>(); // Tenants that should be processed on other servers - private final Map otherTenantStates = new ConcurrentHashMap<>(); + private final Map otherUsageStates = new ConcurrentHashMap<>(); @Value("${usage.stats.report.enabled:true}") private boolean enabled; @@ -151,60 +155,73 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener msg, TbCallback callback) { ToUsageStatsServiceMsg statsMsg = msg.getValue(); TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); - - if (tenantProfileCache.get(tenantId) == null) { - return; + CustomerId customerId; + if (statsMsg.getCustomerIdMSB() != 0 && statsMsg.getCustomerIdLSB() != 0) { + customerId = new CustomerId(new UUID(statsMsg.getCustomerIdMSB(), statsMsg.getCustomerIdLSB())); + } else { + customerId = new CustomerId(EntityId.NULL_UUID); } - TenantApiUsageState tenantState; + processEntityUsageStats(tenantId, customerId.isNullUid() ? tenantId : customerId, statsMsg.getValuesList()); + callback.onSuccess(); + } + + private void processEntityUsageStats(TenantId tenantId, EntityId entityId, List values) { + if (tenantProfileCache.get(tenantId) == null) return; + + BaseApiUsageState usageState; List updatedEntries; Map result; + updateLock.lock(); try { - tenantState = getOrFetchState(tenantId); - long ts = tenantState.getCurrentCycleTs(); - long hourTs = tenantState.getCurrentHourTs(); + usageState = getOrFetchState(tenantId, entityId); + long ts = usageState.getCurrentCycleTs(); + long hourTs = usageState.getCurrentHourTs(); long newHourTs = SchedulerUtils.getStartOfCurrentHour(); if (newHourTs != hourTs) { - tenantState.setHour(newHourTs); + usageState.setHour(newHourTs); } updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); Set apiFeatures = new HashSet<>(); - for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { + for (UsageStatsKVProto kvProto : values) { ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); - long newValue = tenantState.add(recordKey, kvProto.getValue()); + long newValue = usageState.add(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); - long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); + long newHourlyValue = usageState.addToHourly(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(newHourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); apiFeatures.add(recordKey.getApiFeature()); } - result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures); + result = usageState.checkStateUpdatedDueToThreshold(apiFeatures); } finally { updateLock.unlock(); } - tsWsService.saveAndNotifyInternal(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, usageState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK); if (!result.isEmpty()) { - persistAndNotify(tenantState, result); + persistAndNotify(usageState, result); } - callback.onSuccess(); } @Override protected void onTbApplicationEvent(PartitionChangeEvent partitionChangeEvent) { if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { - myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); - otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + myUsageStates.entrySet().removeIf(entry -> { + return !partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition(); + }); + otherUsageStates.entrySet().removeIf(entry -> { + return partitionService.resolve(ServiceType.TB_CORE, entry.getValue().getTenantId(), entry.getKey()).isMyPartition(); + }); initStatesFromDataBase(); } } @Override public ApiUsageState getApiUsageState(TenantId tenantId) { - TenantApiUsageState tenantState = myTenantStates.get(tenantId); + TenantApiUsageState tenantState = (TenantApiUsageState) myUsageStates.get(tenantId); if (tenantState != null) { return tenantState.getApiUsageState(); } else { - ApiUsageState state = otherTenantStates.get(tenantId); + ApiUsageState state = otherUsageStates.get(tenantId); if (state != null) { return state; } else { @@ -213,11 +230,11 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener { - if (tenantProfile.getId().equals(state.getTenantProfileId())) { - updateTenantState(state, tenantProfile); - } - }); + myUsageStates.values().stream() + .filter(state -> state.getEntityType() == EntityType.TENANT) + .map(state -> (TenantApiUsageState) state) + .forEach(state -> { + if (tenantProfile.getId().equals(state.getTenantProfileId())) { + updateTenantState(state, tenantProfile); + } + }); } finally { updateLock.unlock(); } @@ -256,7 +276,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener result) { - log.info("[{}] Detected update of the API state: {}", state.getTenantId(), result); + private void persistAndNotify(BaseApiUsageState state, Map result) { + log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result); apiUsageStateService.update(state.getApiUsageState()); clusterService.onApiStateChange(state.getApiUsageState(), null); long ts = System.currentTimeMillis(); @@ -302,20 +322,21 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); - String email = tenantService.findTenantById(state.getTenantId()).getEmail(); - - if (StringUtils.isNotEmpty(email)) { - result.forEach((apiFeature, stateValue) -> { - mailExecutor.submit(() -> { - try { - 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); - } + if (state.getEntityType() == EntityType.TENANT) { + String email = tenantService.findTenantById(state.getTenantId()).getEmail(); + if (StringUtils.isNotEmpty(email)) { + result.forEach((apiFeature, stateValue) -> { + mailExecutor.submit(() -> { + try { + mailService.sendApiFeatureStateEmail(apiFeature, stateValue, email, createStateMailMessage((TenantApiUsageState) 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); + } + }); }); - }); - } else { - log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); + } else { + log.warn("[{}] Can't send update of the API state to tenant with empty email!", state.getTenantId()); + } } } @@ -350,12 +371,13 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener { + myUsageStates.values().forEach(state -> { if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) { + // FIXME TenantId tenantId = state.getTenantId(); state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values())); - updateTenantState(state, tenantProfileCache.get(tenantId)); + updateTenantState((TenantApiUsageState) state, tenantProfileCache.get(tenantId)); } }); } finally { @@ -363,7 +385,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener keys) { + private void saveNewCounts(BaseApiUsageState state, List keys) { List counts = keys.stream() .map(key -> new BasicTsKvEntry(state.getCurrentCycleTs(), new LongDataEntry(key.getApiCountKey(), 0L))) .collect(Collectors.toList()); @@ -371,13 +393,74 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener { + try { + return apiUsageStateService.createDefaultApiUsageState(tenantId, entityId); + } catch (Exception e) { + return apiUsageStateService.findApiUsageStateByEntityId(entityId); + } + }); + + switch (entityId.getEntityType()) { + case TENANT: + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + state = new TenantApiUsageState(tenantProfile, storedState); + break; + case CUSTOMER: + default: + state = new CustomerApiUsageState(storedState); + break; + } + + List newCounts = new ArrayList<>(); + try { + List dbValues = tsService.findAllLatest(tenantId, storedState.getId()).get(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + boolean cycleEntryFound = false; + boolean hourlyEntryFound = false; + for (TsKvEntry tsKvEntry : dbValues) { + if (tsKvEntry.getKey().equals(key.getApiCountKey())) { + cycleEntryFound = true; + + boolean oldCount = tsKvEntry.getTs() == state.getCurrentCycleTs(); + state.put(key, oldCount ? tsKvEntry.getLongValue().get() : 0L); + + if (!oldCount) { + newCounts.add(key); + } + } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { + hourlyEntryFound = true; + state.putHourly(key, tsKvEntry.getTs() == state.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L); + } + if (cycleEntryFound && hourlyEntryFound) { + break; + } + } + } + log.debug("[{}] Initialized state: {}", entityId, storedState); + myUsageStates.put(entityId, state); + saveNewCounts(state, newCounts); + } catch (InterruptedException | ExecutionException e) { + log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); + } + + return state; + } + private TenantApiUsageState getOrFetchState(TenantId tenantId) { - TenantApiUsageState tenantState = myTenantStates.get(tenantId); + TenantApiUsageState tenantState = (TenantApiUsageState) myUsageStates.get(tenantId); if (tenantState == null) { ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); if (dbStateEntity == null) { try { - dbStateEntity = apiUsageStateService.createDefaultApiUsageState(tenantId); + dbStateEntity = apiUsageStateService.createDefaultApiUsageState(tenantId, null); } catch (Exception e) { dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); } @@ -410,7 +493,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); List> futures = new ArrayList<>(); for (Tenant tenant : tenantIterator) { - if (!myTenantStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { + if (!myUsageStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { log.debug("[{}] Initializing tenant state.", tenant.getId()); futures.add(tmpInitExecutor.submit(() -> { try { 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 bf3ec13893..6687d184d2 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 @@ -22,85 +22,23 @@ 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.ApiUsageStateValue; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; -import org.thingsboard.server.common.msg.tools.SchedulerUtils; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public class TenantApiUsageState { - - private final Map currentCycleValues = new ConcurrentHashMap<>(); - private final Map currentHourValues = new ConcurrentHashMap<>(); +public class TenantApiUsageState extends BaseApiUsageState { @Getter @Setter private TenantProfileId tenantProfileId; @Getter @Setter private TenantProfileData tenantProfileData; - @Getter - private final ApiUsageState apiUsageState; - @Getter - private volatile long currentCycleTs; - @Getter - private volatile long nextCycleTs; - @Getter - private volatile long currentHourTs; public TenantApiUsageState(TenantProfile tenantProfile, ApiUsageState apiUsageState) { + super(apiUsageState); this.tenantProfileId = tenantProfile.getId(); this.tenantProfileData = tenantProfile.getProfileData(); - this.apiUsageState = apiUsageState; - this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); - this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); - this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); - } - - public void put(ApiUsageRecordKey key, Long value) { - currentCycleValues.put(key, value); - } - - public void putHourly(ApiUsageRecordKey key, Long value) { - currentHourValues.put(key, value); - } - - public long add(ApiUsageRecordKey key, long value) { - long result = currentCycleValues.getOrDefault(key, 0L) + value; - currentCycleValues.put(key, result); - return result; - } - - public long get(ApiUsageRecordKey key) { - return currentCycleValues.getOrDefault(key, 0L); - } - - public long addToHourly(ApiUsageRecordKey key, long value) { - long result = currentHourValues.getOrDefault(key, 0L) + value; - currentHourValues.put(key, result); - return result; - } - - public void setHour(long currentHourTs) { - this.currentHourTs = currentHourTs; - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - currentHourValues.put(key, 0L); - } - } - - public void setCycles(long currentCycleTs, long nextCycleTs) { - this.currentCycleTs = currentCycleTs; - this.nextCycleTs = nextCycleTs; - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - currentCycleValues.put(key, 0L); - } } public long getProfileThreshold(ApiUsageRecordKey key) { @@ -111,69 +49,7 @@ public class TenantApiUsageState { return tenantProfileData.getConfiguration().getWarnThreshold(key); } - public TenantId getTenantId() { - return apiUsageState.getTenantId(); - } - - public ApiUsageStateValue getFeatureValue(ApiFeature feature) { - switch (feature) { - case TRANSPORT: - return apiUsageState.getTransportState(); - case RE: - return apiUsageState.getReExecState(); - case DB: - return apiUsageState.getDbStorageState(); - case JS: - return apiUsageState.getJsExecState(); - case EMAIL: - return apiUsageState.getEmailExecState(); - case SMS: - return apiUsageState.getSmsExecState(); - default: - return ApiUsageStateValue.ENABLED; - } - } - - public boolean setFeatureValue(ApiFeature feature, ApiUsageStateValue value) { - ApiUsageStateValue currentValue = getFeatureValue(feature); - switch (feature) { - case TRANSPORT: - apiUsageState.setTransportState(value); - break; - case RE: - apiUsageState.setReExecState(value); - break; - case DB: - apiUsageState.setDbStorageState(value); - break; - case JS: - apiUsageState.setJsExecState(value); - break; - case EMAIL: - apiUsageState.setEmailExecState(value); - break; - case SMS: - apiUsageState.setSmsExecState(value); - break; - } - return !currentValue.equals(value); - } - - public Map checkStateUpdatedDueToThresholds() { - return checkStateUpdatedDueToThreshold(new HashSet<>(Arrays.asList(ApiFeature.values()))); - } - - public Map checkStateUpdatedDueToThreshold(Set features) { - Map result = new HashMap<>(); - for (ApiFeature feature : features) { - Pair tmp = checkStateUpdatedDueToThreshold(feature); - if (tmp != null) { - result.put(tmp.getFirst(), tmp.getSecond()); - } - } - return result; - } - + @Override public Pair checkStateUpdatedDueToThreshold(ApiFeature feature) { ApiUsageStateValue featureValue = ApiUsageStateValue.ENABLED; for (ApiUsageRecordKey recordKey : ApiUsageRecordKey.getKeys(feature)) { @@ -193,4 +69,9 @@ public class TenantApiUsageState { return setFeatureValue(feature, featureValue) ? Pair.of(feature, featureValue) : null; } + @Override + public EntityType getEntityType() { + return EntityType.TENANT; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 70978df0a3..fb01926e2a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -920,7 +920,7 @@ public final class EdgeGrpcSession implements Closeable { try { if (uplinkMsg.getEntityDataCount() > 0) { for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) { - result.addAll(ctx.getTelemetryProcessor().onTelemetryUpdate(edge.getTenantId(), entityData)); + result.addAll(ctx.getTelemetryProcessor().onTelemetryUpdate(edge.getTenantId(), edge.getCustomerId(), entityData)); } } if (uplinkMsg.getDeviceUpdateMsgCount() > 0) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java index 35eea3f962..0cc241c743 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java @@ -233,7 +233,7 @@ public class DeviceProcessor extends BaseProcessor { ObjectNode entityNode = mapper.valueToTree(device); TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, mapper.writeValueAsString(entityNode)); - tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { + tbClusterService.pushMsgToRuleEngine(tenantId, device.getCustomerId(), deviceId, tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { log.debug("Successfully send ENTITY_CREATED EVENT to rule engine [{}]", device); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java index c858955bfc..6667414815 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java @@ -70,7 +70,7 @@ public class TelemetryProcessor extends BaseProcessor { private final Gson gson = new Gson(); - public List> onTelemetryUpdate(TenantId tenantId, EntityDataProto entityData) { + public List> onTelemetryUpdate(TenantId tenantId, CustomerId customerId, EntityDataProto entityData) { log.trace("[{}] onTelemetryUpdate [{}]", tenantId, entityData); List> result = new ArrayList<>(); EntityId entityId = constructEntityId(entityData); @@ -80,14 +80,14 @@ public class TelemetryProcessor extends BaseProcessor { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue(DataConstants.MSG_SOURCE_KEY, DataConstants.EDGE_MSG_SOURCE); if (entityData.hasPostAttributesMsg()) { - result.add(processPostAttributes(tenantId, entityId, entityData.getPostAttributesMsg(), metaData)); + result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData)); } if (entityData.hasAttributesUpdatedMsg()) { metaData.putValue("scope", entityData.getPostAttributeScope()); - result.add(processAttributesUpdate(tenantId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); + result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); } if (entityData.hasPostTelemetryMsg()) { - result.add(processPostTelemetry(tenantId, entityId, entityData.getPostTelemetryMsg(), metaData)); + result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData)); } } if (entityData.hasAttributeDeleteMsg()) { @@ -148,7 +148,7 @@ public class TelemetryProcessor extends BaseProcessor { } } - private ListenableFuture processPostTelemetry(TenantId tenantId, EntityId entityId, TransportProtos.PostTelemetryMsg msg, TbMsgMetaData metaData) { + private ListenableFuture processPostTelemetry(TenantId tenantId, CustomerId customerId, EntityId entityId, TransportProtos.PostTelemetryMsg msg, TbMsgMetaData metaData) { SettableFuture futureToSet = SettableFuture.create(); for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); @@ -157,7 +157,7 @@ public class TelemetryProcessor extends BaseProcessor { String queueName = defaultQueueAndRuleChain.getKey(); RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); TbMsg tbMsg = TbMsg.newMsg(queueName, SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, metaData, gson.toJson(json), ruleChainId, null); - tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + tbClusterService.pushMsgToRuleEngine(tenantId, customerId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); @@ -173,14 +173,14 @@ public class TelemetryProcessor extends BaseProcessor { return futureToSet; } - private ListenableFuture processPostAttributes(TenantId tenantId, EntityId entityId, TransportProtos.PostAttributeMsg msg, TbMsgMetaData metaData) { + private ListenableFuture processPostAttributes(TenantId tenantId, CustomerId customerId, EntityId entityId, TransportProtos.PostAttributeMsg msg, TbMsgMetaData metaData) { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); String queueName = defaultQueueAndRuleChain.getKey(); RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); TbMsg tbMsg = TbMsg.newMsg(queueName, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, metaData, gson.toJson(json), ruleChainId, null); - tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + tbClusterService.pushMsgToRuleEngine(tenantId, customerId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); @@ -195,7 +195,7 @@ public class TelemetryProcessor extends BaseProcessor { return futureToSet; } - private ListenableFuture processAttributesUpdate(TenantId tenantId, EntityId entityId, TransportProtos.PostAttributeMsg msg, TbMsgMetaData metaData) { + private ListenableFuture processAttributesUpdate(TenantId tenantId, CustomerId customerId, EntityId entityId, TransportProtos.PostAttributeMsg msg, TbMsgMetaData metaData) { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); Set attributes = JsonConverter.convertToAttributes(json); @@ -207,7 +207,7 @@ public class TelemetryProcessor extends BaseProcessor { String queueName = defaultQueueAndRuleChain.getKey(); RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); TbMsg tbMsg = TbMsg.newMsg(queueName, DataConstants.ATTRIBUTES_UPDATED, entityId, metaData, gson.toJson(json), ruleChainId, null); - tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + tbClusterService.pushMsgToRuleEngine(tenantId, customerId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); 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 5eba80315c..b4a9d06583 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 @@ -390,7 +390,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService pageData = tenantService.findTenants(pageLink); for (Tenant tenant : pageData.getData()) { try { - apiUsageStateService.createDefaultApiUsageState(tenant.getId()); + apiUsageStateService.createDefaultApiUsageState(tenant.getId(), null); } catch (Exception e) { } List deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get(); 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 972c99f866..b4c1058616 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 @@ -36,6 +36,7 @@ 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.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.exception.IncorrectParameterException; @@ -233,7 +234,7 @@ public class DefaultMailService implements MailService { } @Override - public void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException { + public void send(TenantId tenantId, CustomerId customerId, 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"); @@ -248,7 +249,7 @@ public class DefaultMailService implements MailService { helper.setSubject(subject); helper.setText(body); mailSender.send(helper.getMimeMessage()); - apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); + apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } else { throw new RuntimeException("Email sending is disabled due to API limits!"); } 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 2f5610d7ff..e61983b20d 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 @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; @@ -133,7 +134,12 @@ public class DefaultTbClusterService implements TbClusterService { } @Override - public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) { + public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg msg, TbQueueCallback callback) { + pushMsgToRuleEngine(tenantId, null, entityId, msg, callback); + } + + @Override + public void pushMsgToRuleEngine(TenantId tenantId, CustomerId customerId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) { if (tenantId.isNullUid()) { if (entityId.getEntityType().equals(EntityType.TENANT)) { tenantId = new TenantId(entityId.getId()); @@ -148,6 +154,7 @@ public class DefaultTbClusterService implements TbClusterService { tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId()))); } } + tbMsg.setCustomerId(customerId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId); log.trace("PUSHING msg: {} to:{}", tbMsg, tpi); ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder() 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 c5848bed58..c66ca7a62f 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 @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -50,6 +51,8 @@ public interface TbClusterService { void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg msg, TbQueueCallback callback); + void pushMsgToRuleEngine(TenantId tenantId, CustomerId customerId, EntityId entityId, TbMsg msg, TbQueueCallback callback); + void pushNotificationToRuleEngine(String targetServiceId, FromDeviceRpcResponse response, TbQueueCallback callback); void pushNotificationToTransport(String targetServiceId, ToTransportMsg response, TbQueueCallback callback); diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index a29cac7f47..08d4143140 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -34,6 +34,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.service.security.model.SecurityUser; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -95,11 +96,11 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { } @Override - public void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer responseConsumer) { + public void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer responseConsumer, SecurityUser currentUser) { log.trace("[{}][{}] Processing REST API call to rule engine [{}]", request.getTenantId(), request.getId(), request.getDeviceId()); UUID requestId = request.getId(); localToRuleEngineRpcRequests.put(requestId, responseConsumer); - sendRpcRequestToRuleEngine(request); + sendRpcRequestToRuleEngine(request, currentUser); scheduleToRuleEngineTimeout(request, requestId); } @@ -149,7 +150,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { } } - private void sendRpcRequestToRuleEngine(ToDeviceRpcRequest msg) { + private void sendRpcRequestToRuleEngine(ToDeviceRpcRequest msg, SecurityUser currentUser) { ObjectNode entityNode = json.createObjectNode(); TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("requestUUID", msg.getId().toString()); @@ -168,7 +169,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { try { TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); - clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); + clusterService.pushMsgToRuleEngine(msg.getTenantId(), currentUser.getCustomerId(), msg.getDeviceId(), tbMsg, null); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java index 74e7072611..c3c6f9b888 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbCoreDeviceRpcService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.rpc; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.service.security.model.SecurityUser; import java.util.function.Consumer; @@ -27,11 +28,11 @@ public interface TbCoreDeviceRpcService { /** * Handles REST API calls that contain RPC requests to Device and pushes them to Rule Engine. * Schedules the timeout for the RPC call based on the {@link ToDeviceRpcRequest} - * - * @param request the RPC request + * @param request the RPC request * @param responseConsumer the consumer of the RPC response + * @param currentUser */ - void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer responseConsumer); + void processRestApiRpcRequest(ToDeviceRpcRequest request, Consumer responseConsumer, SecurityUser currentUser); /** * Handles the RPC response from the Rule Engine. diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java index 7342169a86..2f0378ae9d 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; @@ -73,14 +74,14 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { } @Override - public ListenableFuture invokeFunction(TenantId tenantId, UUID scriptId, Object... args) { + public ListenableFuture invokeFunction(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args) { if (apiUsageStateService.getApiUsageState(tenantId).isJsExecEnabled()) { String functionName = scriptIdToNameMap.get(scriptId); if (functionName == null) { return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); } if (!isDisabled(scriptId)) { - apiUsageClient.report(tenantId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); + apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); return doInvokeFunction(scriptId, functionName, args); } else { return Futures.immediateFailedFuture( diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java index 1636b2cbf0..317170f1a7 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java @@ -16,7 +16,7 @@ package org.thingsboard.server.service.script; import com.google.common.util.concurrent.ListenableFuture; -import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import java.util.UUID; @@ -25,7 +25,7 @@ public interface JsInvokeService { ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames); - ListenableFuture invokeFunction(TenantId tenantId, UUID scriptId, Object... args); + ListenableFuture invokeFunction(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args); ListenableFuture release(UUID scriptId); diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index 5dffc0217c..066ce71a58 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java @@ -218,7 +218,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S private JsonNode executeScript(TbMsg msg) throws ScriptException { try { String[] inArgs = prepareArgs(msg); - String eval = sandboxService.invokeFunction(tenantId, this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); + String eval = sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); return mapper.readTree(eval); } catch (ExecutionException e) { if (e.getCause() instanceof ScriptException) { @@ -235,7 +235,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S private ListenableFuture executeScriptAsync(TbMsg msg) { String[] inArgs = prepareArgs(msg); - return Futures.transformAsync(sandboxService.invokeFunction(tenantId, this.scriptId, inArgs[0], inArgs[1], inArgs[2]), + return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]), o -> { try { return Futures.immediateFuture(mapper.readTree(o.toString())); 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 f7a012b23e..f0b3eb174e 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 @@ -22,6 +22,7 @@ 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.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.sms.config.SmsProviderConfiguration; import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.AdminSettings; @@ -94,7 +95,7 @@ public class DefaultSmsService implements SmsService { } @Override - public void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException { + public void sendSms(TenantId tenantId, CustomerId customerId, String[] numbersTo, String message) throws ThingsboardException { if (apiUsageStateService.getApiUsageState(tenantId).isSmsSendEnabled()) { int smsCount = 0; try { @@ -103,7 +104,7 @@ public class DefaultSmsService implements SmsService { } } finally { if (smsCount > 0) { - apiUsageClient.report(tenantId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); + apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.SMS_EXEC_COUNT, smsCount); } } } else { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index c8a4e80e0e..6ccfd255ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -468,6 +468,7 @@ public class DefaultDeviceStateService extends TbApplicationEventListener ts, FutureCallback callback) { - saveAndNotify(tenantId, entityId, ts, 0L, callback); + saveAndNotify(tenantId, null, entityId, ts, 0L, callback); } @Override - public void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { + public void saveAndNotify(TenantId tenantId, CustomerId customerId, EntityId entityId, List ts, long ttl, FutureCallback callback) { checkInternalEntity(entityId); boolean sysTenant = TenantId.SYS_TENANT_ID.equals(tenantId) || tenantId == null; if (sysTenant || apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { @@ -127,7 +125,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void onSuccess(Integer result) { if (!sysTenant && result != null && result > 0) { - apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); + apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); } callback.onSuccess(null); } 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 6944809515..96bc2fc02b 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 @@ -81,6 +81,7 @@ import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -273,7 +274,7 @@ public class DefaultTransportApiService implements TransportApiService { DeviceId deviceId = device.getId(); ObjectNode entityNode = mapper.valueToTree(device); TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, metaData, TbMsgDataType.JSON, mapper.writeValueAsString(entityNode)); - tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, null); + tbClusterService.pushMsgToRuleEngine(tenantId, customerId, deviceId, tbMsg, null); } GetOrCreateDeviceFromGatewayResponseMsg.Builder builder = GetOrCreateDeviceFromGatewayResponseMsg.newBuilder() .setDeviceInfo(getDeviceInfoProto(device)); @@ -411,6 +412,8 @@ public class DefaultTransportApiService implements TransportApiService { return DeviceInfoProto.newBuilder() .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) .setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(device.getCustomerId().getId().getMostSignificantBits()) + .setCustomerIdLSB(device.getCustomerId().getId().getLeastSignificantBits()) .setDeviceIdMSB(device.getId().getId().getMostSignificantBits()) .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits()) .setDeviceName(device.getName()) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java index 2249940c48..2532fbc00d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java @@ -21,12 +21,12 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.junit.After; import org.junit.Assert; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; @@ -750,7 +750,7 @@ public class BaseWebsocketApiTest extends AbstractWebsocketTest { private void sendTelemetry(Device device, List tsData) throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); - tsService.saveAndNotify(device.getTenantId(), device.getId(), tsData, 0, new FutureCallback() { + tsService.saveAndNotify(device.getTenantId(), null, device.getId(), tsData, 0, new FutureCallback() { @Override public void onSuccess(@Nullable Void result) { latch.countDown(); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java index 5b0985e66c..6cd9c62aef 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java @@ -17,16 +17,19 @@ package org.thingsboard.server.dao.usagerecord; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.ApiUsageStateId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; public interface ApiUsageStateService { - ApiUsageState createDefaultApiUsageState(TenantId id); + ApiUsageState createDefaultApiUsageState(TenantId id, EntityId entityId); ApiUsageState update(ApiUsageState apiUsageState); ApiUsageState findTenantApiUsageState(TenantId tenantId); + ApiUsageState findApiUsageStateByEntityId(EntityId entityId); + void deleteApiUsageStateByTenantId(TenantId tenantId); ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 66a3670ddd..072626b04e 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -23,6 +23,7 @@ import lombok.Builder; import lombok.Data; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.RuleChainId; @@ -47,6 +48,7 @@ public final class TbMsg implements Serializable { private final long ts; private final String type; private final EntityId originator; + private CustomerId customerId; private final TbMsgMetaData metaData; private final TbMsgDataType dataType; private final String data; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java index 01a322f158..556b9b88ff 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java @@ -19,6 +19,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -31,6 +34,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import javax.annotation.PostConstruct; +import java.util.EnumMap; import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -47,8 +51,9 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { @Value("${usage.stats.report.interval:10}") private int interval; - @SuppressWarnings("unchecked") - private final ConcurrentMap[] values = new ConcurrentMap[ApiUsageRecordKey.values().length]; + private final EnumMap> stats = new EnumMap<>(ApiUsageRecordKey.class); + private final ConcurrentMap tenants = new ConcurrentHashMap<>(); + private final PartitionService partitionService; private final SchedulerComponent scheduler; private final TbQueueProducerProvider producerProvider; @@ -65,55 +70,88 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { if (enabled) { msgProducer = this.producerProvider.getTbUsageStatsMsgProducer(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - values[key.ordinal()] = new ConcurrentHashMap<>(); + stats.put(key, new ConcurrentHashMap<>()); } - scheduler.scheduleWithFixedDelay(this::reportStats, new Random().nextInt(interval), interval, TimeUnit.SECONDS); + scheduler.scheduleWithFixedDelay(() -> { + try { + reportStats(); + } catch (Exception e) { + log.warn("Failed to report statistics: ", e); + } + }, new Random().nextInt(interval), interval, TimeUnit.SECONDS); } } private void reportStats() { - try { - ConcurrentMap report = new ConcurrentHashMap<>(); + ConcurrentMap report = new ConcurrentHashMap<>(); - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - values[key.ordinal()].forEach(((tenantId, atomicLong) -> { - long value = atomicLong.getAndSet(0); - if (value > 0) { - ToUsageStatsServiceMsg.Builder msgBuilder = report.computeIfAbsent(tenantId, id -> { - ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder(); - msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); - msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); - return msg; - }); - msgBuilder.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(value).build()); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + ConcurrentMap statsForKey = stats.get(key); + statsForKey.forEach((initiatorId, statsValue) -> { + long value = statsValue.get(); + if (value == 0) return; + + ToUsageStatsServiceMsg.Builder statsMsgBuilder = report.computeIfAbsent(initiatorId, id -> { + ToUsageStatsServiceMsg.Builder newStatsMsgBuilder = ToUsageStatsServiceMsg.newBuilder(); + + TenantId tenantId; + if (initiatorId.getEntityType() == EntityType.TENANT) { + tenantId = (TenantId) initiatorId; + } else { + tenantId = tenants.get(initiatorId); } - })); - } - report.forEach(((tenantId, builder) -> { - //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).newByTopic(msgProducer.getDefaultTopic()); - msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); - })); - if (!report.isEmpty()) { - log.info("Report statistics for: {} tenants", report.size()); + newStatsMsgBuilder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); + newStatsMsgBuilder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); + + if (initiatorId.getEntityType() == EntityType.CUSTOMER) { + newStatsMsgBuilder.setCustomerIdMSB(initiatorId.getId().getMostSignificantBits()); + newStatsMsgBuilder.setCustomerIdLSB(initiatorId.getId().getLeastSignificantBits()); + } + + return newStatsMsgBuilder; + }); + + statsMsgBuilder.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(value).build()); + }); + statsForKey.clear(); + } + + report.forEach(((initiatorId, builder) -> { + //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? + + TenantId tenantId; + if (initiatorId.getEntityType() == EntityType.TENANT) { + tenantId = (TenantId) initiatorId; + } else { + tenantId = tenants.get(initiatorId); } - } catch (Exception e) { - log.warn("Failed to report statistics: ", e); + + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, initiatorId).newByTopic(msgProducer.getDefaultTopic()); + msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); + })); + + if (!report.isEmpty()) { + log.info("Reporting API usage statistics for {} tenants and customers", report.size()); } } @Override - public void report(TenantId tenantId, ApiUsageRecordKey key, long value) { + public void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key, long value) { if (enabled) { - ConcurrentMap map = values[key.ordinal()]; - AtomicLong atomicValue = map.computeIfAbsent(tenantId, id -> new AtomicLong()); - atomicValue.addAndGet(value); + ConcurrentMap statsForKey = stats.get(key); + + statsForKey.computeIfAbsent(tenantId, id -> new AtomicLong()).addAndGet(value); + + if (customerId != null && !customerId.isNullUid()) { + statsForKey.computeIfAbsent(customerId, id -> new AtomicLong()).addAndGet(value); + tenants.putIfAbsent(customerId, tenantId); + } } } @Override - public void report(TenantId tenantId, ApiUsageRecordKey key) { - report(tenantId, key, 1L); + public void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key) { + report(tenantId, customerId, key, 1); } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java index f46a2d0143..be1b071873 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java @@ -16,12 +16,13 @@ package org.thingsboard.server.queue.usagestats; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; public interface TbApiUsageClient { - void report(TenantId tenantId, ApiUsageRecordKey key, long value); + void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key, long value); - void report(TenantId tenantId, ApiUsageRecordKey key); + void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key); } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 2150992bbb..d599031b34 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -53,6 +53,8 @@ message SessionInfoProto { int64 gwSessionIdLSB = 11; int64 deviceProfileIdMSB = 12; int64 deviceProfileIdLSB = 13; + int64 customerIdMSB = 14; + int64 customerIdLSB = 15; } enum SessionEvent { @@ -110,6 +112,8 @@ message DeviceInfoProto { string additionalInfo = 7; int64 deviceProfileIdMSB = 8; int64 deviceProfileIdLSB = 9; + int64 customerIdMSB = 10; + int64 customerIdLSB = 11; } /** @@ -638,4 +642,6 @@ message ToUsageStatsServiceMsg { int64 entityIdMSB = 3; int64 entityIdLSB = 4; repeated UsageStatsKVProto values = 5; + int64 customerIdMSB = 6; + int64 customerIdLSB = 7; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java index b175ca8580..63d19b9c30 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/SessionInfoCreator.java @@ -40,6 +40,8 @@ public class SessionInfoCreator { .setDeviceIdLSB(msg.getDeviceInfo().getDeviceId().getId().getLeastSignificantBits()) .setTenantIdMSB(msg.getDeviceInfo().getTenantId().getId().getMostSignificantBits()) .setTenantIdLSB(msg.getDeviceInfo().getTenantId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(msg.getDeviceInfo().getCustomerId().getId().getMostSignificantBits()) + .setCustomerIdLSB(msg.getDeviceInfo().getCustomerId().getId().getLeastSignificantBits()) .setDeviceName(msg.getDeviceInfo().getDeviceName()) .setDeviceType(msg.getDeviceInfo().getDeviceType()) .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits()) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/TransportDeviceInfo.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/TransportDeviceInfo.java index 20d01420c4..27d42d867f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/TransportDeviceInfo.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/TransportDeviceInfo.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.transport.auth; import lombok.Data; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; @@ -24,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; public class TransportDeviceInfo { private TenantId tenantId; + private CustomerId customerId; private DeviceProfileId deviceProfileId; private DeviceId deviceId; private String deviceName; 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 740a2a622f..dfc0e6400d 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 @@ -33,8 +33,10 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; 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; @@ -359,6 +361,7 @@ public class DefaultTransportService implements TransportService { private TransportDeviceInfo getTransportDeviceInfo(TransportProtos.DeviceInfoProto di) { TransportDeviceInfo tdi = new TransportDeviceInfo(); tdi.setTenantId(new TenantId(new UUID(di.getTenantIdMSB(), di.getTenantIdLSB()))); + tdi.setCustomerId(new CustomerId(new UUID(di.getCustomerIdMSB(), di.getCustomerIdLSB()))); tdi.setDeviceId(new DeviceId(new UUID(di.getDeviceIdMSB(), di.getDeviceIdLSB()))); tdi.setDeviceProfileId(new DeviceProfileId(new UUID(di.getDeviceProfileIdMSB(), di.getDeviceProfileIdLSB()))); tdi.setAdditionalInfo(di.getAdditionalInfo()); @@ -404,9 +407,9 @@ public class DefaultTransportService implements TransportService { } if (checkLimits(sessionInfo, msg, callback, dataPoints)) { reportActivityInternal(sessionInfo); - TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); + TenantId tenantId = getTenantId(sessionInfo); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), new ApiStatsProxyCallback<>(tenantId, dataPoints, callback)); + MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), new ApiStatsProxyCallback<>(tenantId, getCustomerId(sessionInfo), dataPoints, callback)); for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); @@ -423,7 +426,7 @@ public class DefaultTransportService implements TransportService { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount())) { reportActivityInternal(sessionInfo); - TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); + TenantId tenantId = getTenantId(sessionInfo); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); TbMsgMetaData metaData = new TbMsgMetaData(); @@ -431,7 +434,7 @@ public class DefaultTransportService implements TransportService { metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("notifyDevice", "false"); sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, - new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, msg.getKvList().size(), callback))); + new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, getCustomerId(sessionInfo), msg.getKvList().size(), callback))); } } @@ -440,7 +443,7 @@ public class DefaultTransportService implements TransportService { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setGetAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); + .setGetAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); } } @@ -449,8 +452,8 @@ public class DefaultTransportService implements TransportService { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); - sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setSubscribeToAttributes(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToAttributes(msg).build(), + new ApiStatsProxyCallback<>(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); } } @@ -459,8 +462,8 @@ public class DefaultTransportService implements TransportService { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); - sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setSubscribeToRPC(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setSubscribeToRPC(msg).build(), + new ApiStatsProxyCallback<>(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); } } @@ -468,8 +471,8 @@ public class DefaultTransportService implements TransportService { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); - sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) - .setToDeviceRPCCallResponse(msg).build(), new ApiStatsProxyCallback<>(getTenantId(sessionInfo), 1, callback)); + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setToDeviceRPCCallResponse(msg).build(), + new ApiStatsProxyCallback<>(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); } } @@ -797,6 +800,16 @@ public class DefaultTransportService implements TransportService { return new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); } + protected CustomerId getCustomerId(TransportProtos.SessionInfoProto sessionInfo) { + long msb = sessionInfo.getCustomerIdMSB(); + long lsb = sessionInfo.getCustomerIdLSB(); + if (msb != 0 && lsb != 0) { + return new CustomerId(new UUID(msb, lsb)); + } else { + return new CustomerId(EntityId.NULL_UUID); + } + } + protected DeviceId getDeviceId(TransportProtos.SessionInfoProto sessionInfo) { return new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); } @@ -922,11 +935,13 @@ public class DefaultTransportService implements TransportService { private class ApiStatsProxyCallback implements TransportServiceCallback { private final TenantId tenantId; + private final CustomerId customerId; private final int dataPoints; private final TransportServiceCallback callback; - public ApiStatsProxyCallback(TenantId tenantId, int dataPoints, TransportServiceCallback callback) { + public ApiStatsProxyCallback(TenantId tenantId, CustomerId customerId, int dataPoints, TransportServiceCallback callback) { this.tenantId = tenantId; + this.customerId = customerId; this.dataPoints = dataPoints; this.callback = callback; } @@ -934,8 +949,8 @@ public class DefaultTransportService implements TransportService { @Override public void onSuccess(T msg) { try { - apiUsageClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1); - apiUsageClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints); + apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1); + apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints); } finally { callback.onSuccess(msg); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java index 46cc6eaa83..ac0aea02da 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java @@ -33,6 +33,8 @@ public interface ApiUsageStateRepository extends CrudRepository { */ ApiUsageState findTenantApiUsageState(UUID tenantId); + ApiUsageState findApiUsageStateByEntityId(EntityId entityId); + /** * Delete usage record by tenantId. * 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 f263de1505..a1e882607f 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 @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; @@ -68,12 +69,12 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A } @Override - public ApiUsageState createDefaultApiUsageState(TenantId tenantId) { + public ApiUsageState createDefaultApiUsageState(TenantId tenantId, EntityId entityId) { log.trace("Executing createDefaultUsageRecord [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); ApiUsageState apiUsageState = new ApiUsageState(); apiUsageState.setTenantId(tenantId); - apiUsageState.setEntityId(tenantId); + apiUsageState.setEntityId(entityId); apiUsageState.setTransportState(ApiUsageStateValue.ENABLED); apiUsageState.setReExecState(ApiUsageStateValue.ENABLED); apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); @@ -87,6 +88,7 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); + List apiUsageStates = new ArrayList<>(); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); @@ -126,6 +128,12 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A return apiUsageStateDao.findTenantApiUsageState(tenantId.getId()); } + @Override + public ApiUsageState findApiUsageStateByEntityId(EntityId entityId) { + validateId(entityId.getId(), "Invalid entity id"); + return apiUsageStateDao.findApiUsageStateByEntityId(entityId); + } + @Override public ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id) { log.trace("Executing findApiUsageStateById, tenantId [{}], apiUsageStateId [{}]", tenantId, id); @@ -148,10 +156,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A } if (apiUsageState.getEntityId() == null) { throw new DataValidationException("UsageRecord should be assigned to entity!"); - } else if (!EntityType.TENANT.equals(apiUsageState.getEntityId().getEntityType())) { - throw new DataValidationException("Only Tenant Usage Records are supported!"); - } else if (!apiUsageState.getTenantId().getId().equals(apiUsageState.getEntityId().getId())) { - throw new DataValidationException("Can't assign one Usage Record to multiple tenants!"); + } else if (apiUsageState.getEntityId().getEntityType() != EntityType.TENANT && apiUsageState.getEntityId().getEntityType() != EntityType.CUSTOMER) { + throw new DataValidationException("Only Tenant and Customer Usage Records are supported!"); } } }; 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 7160c150f4..0b957c8b23 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.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import javax.mail.MessagingException; @@ -40,7 +41,7 @@ public interface MailService { void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; - void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body) throws MessagingException; + void send(TenantId tenantId, CustomerId customerId, 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/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java index 224a10f701..e641c83144 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.api; import com.google.common.util.concurrent.FutureCallback; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -31,7 +32,7 @@ public interface RuleEngineTelemetryService { void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback); - void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback); + void saveAndNotify(TenantId tenantId, CustomerId id, EntityId entityId, List ts, long ttl, FutureCallback callback); void saveAndNotify(TenantId tenantId, EntityId entityId, String scope, List attributes, FutureCallback callback); 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 e0190177f0..caf96a68e6 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 @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.api; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; @@ -23,7 +24,7 @@ public interface SmsService { void updateSmsConfiguration(); - void sendSms(TenantId tenantId, String[] numbersTo, String message) throws ThingsboardException;; + void sendSms(TenantId tenantId, CustomerId customerId, String[] numbersTo, String message) throws ThingsboardException;; void sendTestSms(TestSmsRequest testSmsRequest) throws ThingsboardException; 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 22aefd16f1..45e6efadb2 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,7 +26,6 @@ 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; @@ -76,7 +75,7 @@ public class TbSendEmailNode implements TbNode { validateType(msg.getType()); EmailPojo email = getEmail(msg); withCallback(ctx.getMailExecutor().executeAsync(() -> { - sendEmail(ctx, email); + sendEmail(ctx, msg, email); return null; }), ok -> ctx.tellSuccess(msg), @@ -86,9 +85,9 @@ public class TbSendEmailNode implements TbNode { } } - private void sendEmail(TbContext ctx, EmailPojo email) throws Exception { + private void sendEmail(TbContext ctx, TbMsg msg, EmailPojo email) throws Exception { if (this.config.isUseSystemSmtpSettings()) { - ctx.getMailService().send(ctx.getTenantId(), email.getFrom(), email.getTo(), email.getCc(), + ctx.getMailService().send(ctx.getTenantId(), msg.getCustomerId(), 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 df75afe086..22d293c69a 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 @@ -76,7 +76,7 @@ public class TbSendSmsNode implements TbNode { String message = TbNodeUtils.processPattern(this.config.getSmsMessageTemplate(), msg); String[] numbersToList = numbersTo.split(","); if (this.config.isUseSystemSmsSettings()) { - ctx.getSmsService().sendSms(ctx.getTenantId(), numbersToList, message); + ctx.getSmsService().sendSms(ctx.getTenantId(), msg.getCustomerId(), numbersToList, message); } else { for (String numberTo : numbersToList) { this.smsSender.sendSms(numberTo, message); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java index dc657cfce6..c99a575cc3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java @@ -25,6 +25,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -93,7 +94,7 @@ public class TbMsgTimeseriesNode implements TbNode { if (ttl == 0L) { ttl = tenantProfileDefaultStorageTtl; } - ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg)); + ctx.getTelemetryService().saveAndNotify(ctx.getTenantId(), msg.getCustomerId(), msg.getOriginator(), tsKvEntryList, ttl, new TelemetryNodeCallback(ctx, msg)); } public static long getTs(TbMsg msg) { From ecf86b53bab536495143e260b4bfbebebe88a49e Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 19 Apr 2021 12:42:58 +0300 Subject: [PATCH 052/127] Provide usage stats for sysTenant; refactor --- .../service/apiusage/BaseApiUsageState.java | 32 +++-- .../apiusage/CustomerApiUsageState.java | 24 ++-- .../DefaultTbApiUsageStateService.java | 126 ++++++------------ .../service/apiusage/TenantApiUsageState.java | 29 +++- .../usagestats/DefaultTbApiUsageClient.java | 1 + .../usagerecord/ApiUsageStateServiceImpl.java | 25 ++-- 6 files changed, 116 insertions(+), 121 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java index 37a2d986ac..f22fd1c6e7 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/BaseApiUsageState.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2021 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.apiusage; import lombok.Getter; @@ -121,23 +136,6 @@ public abstract class BaseApiUsageState { return !currentValue.equals(value); } - public Map checkStateUpdatedDueToThresholds() { - return checkStateUpdatedDueToThreshold(new HashSet<>(Arrays.asList(ApiFeature.values()))); - } - - public Map checkStateUpdatedDueToThreshold(Set features) { - Map result = new HashMap<>(); - for (ApiFeature feature : features) { - Pair tmp = checkStateUpdatedDueToThreshold(feature); - if (tmp != null) { - result.put(tmp.getFirst(), tmp.getSecond()); - } - } - return result; - } - - public abstract Pair checkStateUpdatedDueToThreshold(ApiFeature feature); - public abstract EntityType getEntityType(); public TenantId getTenantId() { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java index 76b3e63fc6..afc2c47bdb 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/CustomerApiUsageState.java @@ -1,9 +1,21 @@ +/** + * Copyright © 2016-2021 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.apiusage; -import org.springframework.data.util.Pair; -import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageState; -import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.EntityType; public class CustomerApiUsageState extends BaseApiUsageState { @@ -11,12 +23,6 @@ public class CustomerApiUsageState extends BaseApiUsageState { super(apiUsageState); } - @Override - public Pair checkStateUpdatedDueToThreshold(ApiFeature feature) { - ApiUsageStateValue featureValue = ApiUsageStateValue.ENABLED; - return setFeatureValue(feature, featureValue) ? Pair.of(feature, featureValue) : null; - } - @Override public EntityType getEntityType() { return EntityType.CUSTOMER; 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 fb77a65255..22a734a804 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 @@ -48,6 +48,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.customer.CustomerService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -66,10 +67,10 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -99,6 +100,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener msg, TbCallback callback) { ToUsageStatsServiceMsg statsMsg = msg.getValue(); + TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); - CustomerId customerId; + EntityId initiatorId; if (statsMsg.getCustomerIdMSB() != 0 && statsMsg.getCustomerIdLSB() != 0) { - customerId = new CustomerId(new UUID(statsMsg.getCustomerIdMSB(), statsMsg.getCustomerIdLSB())); + initiatorId = new CustomerId(new UUID(statsMsg.getCustomerIdMSB(), statsMsg.getCustomerIdLSB())); } else { - customerId = new CustomerId(EntityId.NULL_UUID); + initiatorId = tenantId; } - processEntityUsageStats(tenantId, customerId.isNullUid() ? tenantId : customerId, statsMsg.getValuesList()); + processEntityUsageStats(tenantId, initiatorId, statsMsg.getValuesList()); callback.onSuccess(); } private void processEntityUsageStats(TenantId tenantId, EntityId entityId, List values) { - if (tenantProfileCache.get(tenantId) == null) return; - BaseApiUsageState usageState; List updatedEntries; Map result; @@ -192,7 +196,11 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener stateTelemetry = new ArrayList<>(); - result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); + 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); - if (state.getEntityType() == EntityType.TENANT) { + if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) { String email = tenantService.findTenantById(state.getTenantId()).getEmail(); if (StringUtils.isNotEmpty(email)) { result.forEach((apiFeature, stateValue) -> { @@ -373,11 +381,12 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener { if ((state.getNextCycleTs() < now) && (now - state.getNextCycleTs() < TimeUnit.HOURS.toMillis(1))) { - // FIXME - TenantId tenantId = state.getTenantId(); state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); saveNewCounts(state, Arrays.asList(ApiUsageRecordKey.values())); - updateTenantState((TenantApiUsageState) state, tenantProfileCache.get(tenantId)); + if (state.getEntityType() == EntityType.TENANT && !state.getEntityId().equals(TenantId.SYS_TENANT_ID)) { + TenantId tenantId = state.getTenantId(); + updateTenantState((TenantApiUsageState) state, tenantProfileCache.get(tenantId)); + } } }); } finally { @@ -394,29 +403,30 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener { - try { - return apiUsageStateService.createDefaultApiUsageState(tenantId, entityId); - } catch (Exception e) { - return apiUsageStateService.findApiUsageStateByEntityId(entityId); - } - }); - - switch (entityId.getEntityType()) { - case TENANT: - TenantProfile tenantProfile = tenantProfileCache.get(tenantId); - state = new TenantApiUsageState(tenantProfile, storedState); - break; - case CUSTOMER: - default: - state = new CustomerApiUsageState(storedState); - break; + ApiUsageState storedState = apiUsageStateService.findApiUsageStateByEntityId(entityId); + if (storedState == null) { + try { + storedState = apiUsageStateService.createDefaultApiUsageState(tenantId, entityId); + } catch (Exception e) { + storedState = apiUsageStateService.findApiUsageStateByEntityId(entityId); + } + } + if (entityId.getEntityType() == EntityType.TENANT) { + if (!entityId.equals(TenantId.SYS_TENANT_ID)) { + state = new TenantApiUsageState(tenantProfileCache.get((TenantId) entityId), storedState); + } else { + state = new TenantApiUsageState(storedState); + } + } else { + state = new CustomerApiUsageState(storedState); } List newCounts = new ArrayList<>(); @@ -454,54 +464,6 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener newCounts = new ArrayList<>(); - try { - List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get(); - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - boolean cycleEntryFound = false; - boolean hourlyEntryFound = false; - for (TsKvEntry tsKvEntry : dbValues) { - if (tsKvEntry.getKey().equals(key.getApiCountKey())) { - cycleEntryFound = true; - - boolean oldCount = tsKvEntry.getTs() == tenantState.getCurrentCycleTs(); - tenantState.put(key, oldCount ? tsKvEntry.getLongValue().get() : 0L); - - if (!oldCount) { - newCounts.add(key); - } - } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { - hourlyEntryFound = true; - tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L); - } - if (cycleEntryFound && hourlyEntryFound) { - break; - } - } - } - log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity); - myUsageStates.put(tenantId, tenantState); - saveNewCounts(tenantState, newCounts); - } catch (InterruptedException | ExecutionException e) { - log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); - } - } - return tenantState; - } - private void initStatesFromDataBase() { try { log.info("Initializing tenant states."); @@ -516,7 +478,7 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener { try { - updateTenantState(getOrFetchState(tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); + updateTenantState((TenantApiUsageState) getOrFetchState(tenant.getId(), tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); log.debug("[{}] Initialized tenant state.", tenant.getId()); } catch (Exception e) { log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); 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 6687d184d2..8bad7edc6e 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 @@ -27,6 +27,12 @@ import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + public class TenantApiUsageState extends BaseApiUsageState { @Getter @Setter @@ -41,6 +47,10 @@ public class TenantApiUsageState extends BaseApiUsageState { this.tenantProfileData = tenantProfile.getProfileData(); } + public TenantApiUsageState(ApiUsageState apiUsageState) { + super(apiUsageState); + } + public long getProfileThreshold(ApiUsageRecordKey key) { return tenantProfileData.getConfiguration().getProfileThreshold(key); } @@ -49,8 +59,7 @@ public class TenantApiUsageState extends BaseApiUsageState { return tenantProfileData.getConfiguration().getWarnThreshold(key); } - @Override - public Pair checkStateUpdatedDueToThreshold(ApiFeature feature) { + private Pair checkStateUpdatedDueToThreshold(ApiFeature feature) { ApiUsageStateValue featureValue = ApiUsageStateValue.ENABLED; for (ApiUsageRecordKey recordKey : ApiUsageRecordKey.getKeys(feature)) { long value = get(recordKey); @@ -69,6 +78,22 @@ public class TenantApiUsageState extends BaseApiUsageState { return setFeatureValue(feature, featureValue) ? Pair.of(feature, featureValue) : null; } + + public Map checkStateUpdatedDueToThresholds() { + return checkStateUpdatedDueToThreshold(new HashSet<>(Arrays.asList(ApiFeature.values()))); + } + + public Map checkStateUpdatedDueToThreshold(Set features) { + Map result = new HashMap<>(); + for (ApiFeature feature : features) { + Pair tmp = checkStateUpdatedDueToThreshold(feature); + if (tmp != null) { + result.put(tmp.getFirst(), tmp.getSecond()); + } + } + return result; + } + @Override public EntityType getEntityType() { return EntityType.TENANT; diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java index 556b9b88ff..efe1f63351 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java @@ -142,6 +142,7 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { ConcurrentMap statsForKey = stats.get(key); statsForKey.computeIfAbsent(tenantId, id -> new AtomicLong()).addAndGet(value); + statsForKey.computeIfAbsent(TenantId.SYS_TENANT_ID, id -> new AtomicLong()).addAndGet(value); if (customerId != null && !customerId.isNullUid()) { statsForKey.computeIfAbsent(customerId, id -> new AtomicLong()).addAndGet(value); 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 a1e882607f..31f9f8f437 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 @@ -85,10 +85,6 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); - Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); - TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); - TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); - List apiUsageStates = new ArrayList<>(); apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); @@ -104,12 +100,19 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A new StringDataEntry(ApiFeature.SMS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); - List profileThresholds = new ArrayList<>(); - - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); + if (entityId.getEntityType() == EntityType.TENANT && !entityId.equals(TenantId.SYS_TENANT_ID)) { + tenantId = (TenantId) entityId; + Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); + TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); + TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); + + List profileThresholds = new ArrayList<>(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); + } + tsService.save(tenantId, saved.getId(), profileThresholds, 0L); } - tsService.save(tenantId, saved.getId(), profileThresholds, 0L); + return saved; } @@ -150,8 +153,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A throw new DataValidationException("ApiUsageState should be assigned to tenant!"); } else { Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Asset is referencing to non-existent tenant!"); + if (tenant == null && !requestTenantId.equals(TenantId.SYS_TENANT_ID)) { + throw new DataValidationException("ApiUsageState is referencing to non-existent tenant!"); } } if (apiUsageState.getEntityId() == null) { From 62979e8006dfd6834359af3a121494dc42fd3b21 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 19 Apr 2021 14:11:18 +0300 Subject: [PATCH 053/127] Refactor --- .../server/dao/usagerecord/ApiUsageStateServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 31f9f8f437..9e23cd843a 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 @@ -41,6 +41,7 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -70,7 +71,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A @Override public ApiUsageState createDefaultApiUsageState(TenantId tenantId, EntityId entityId) { - log.trace("Executing createDefaultUsageRecord [{}]", tenantId); + entityId = Objects.requireNonNullElse(entityId, tenantId); + log.trace("Executing createDefaultUsageRecord [{}]", entityId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); ApiUsageState apiUsageState = new ApiUsageState(); apiUsageState.setTenantId(tenantId); From 9d045e231b2e16a61760470e99aab2050d48e872 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 20 Apr 2021 07:46:18 +0300 Subject: [PATCH 054/127] Refactor DefaultTbApiUsageClient --- .../DefaultTbApiUsageStateService.java | 8 +-- .../usagestats/DefaultTbApiUsageClient.java | 67 ++++++++++--------- 2 files changed, 40 insertions(+), 35 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 22a734a804..08e81f28d7 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 @@ -161,14 +161,14 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener> stats = new EnumMap<>(ApiUsageRecordKey.class); - private final ConcurrentMap tenants = new ConcurrentHashMap<>(); + private final EnumMap> stats = new EnumMap<>(ApiUsageRecordKey.class); private final PartitionService partitionService; private final SchedulerComponent scheduler; @@ -83,30 +84,25 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { } private void reportStats() { - ConcurrentMap report = new ConcurrentHashMap<>(); + ConcurrentMap report = new ConcurrentHashMap<>(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - ConcurrentMap statsForKey = stats.get(key); - statsForKey.forEach((initiatorId, statsValue) -> { + ConcurrentMap statsForKey = stats.get(key); + statsForKey.forEach((ownerId, statsValue) -> { long value = statsValue.get(); if (value == 0) return; - ToUsageStatsServiceMsg.Builder statsMsgBuilder = report.computeIfAbsent(initiatorId, id -> { + ToUsageStatsServiceMsg.Builder statsMsgBuilder = report.computeIfAbsent(ownerId, id -> { ToUsageStatsServiceMsg.Builder newStatsMsgBuilder = ToUsageStatsServiceMsg.newBuilder(); - TenantId tenantId; - if (initiatorId.getEntityType() == EntityType.TENANT) { - tenantId = (TenantId) initiatorId; - } else { - tenantId = tenants.get(initiatorId); - } - + TenantId tenantId = ownerId.getTenantId(); newStatsMsgBuilder.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); newStatsMsgBuilder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); - if (initiatorId.getEntityType() == EntityType.CUSTOMER) { - newStatsMsgBuilder.setCustomerIdMSB(initiatorId.getId().getMostSignificantBits()); - newStatsMsgBuilder.setCustomerIdLSB(initiatorId.getId().getLeastSignificantBits()); + EntityId entityId = ownerId.getEntityId(); + if (entityId != null && entityId.getEntityType() == EntityType.CUSTOMER) { + newStatsMsgBuilder.setCustomerIdMSB(entityId.getId().getMostSignificantBits()); + newStatsMsgBuilder.setCustomerIdLSB(entityId.getId().getLeastSignificantBits()); } return newStatsMsgBuilder; @@ -117,18 +113,13 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { statsForKey.clear(); } - report.forEach(((initiatorId, builder) -> { + report.forEach(((ownerId, statsMsg) -> { //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? - TenantId tenantId; - if (initiatorId.getEntityType() == EntityType.TENANT) { - tenantId = (TenantId) initiatorId; - } else { - tenantId = tenants.get(initiatorId); - } - - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, initiatorId).newByTopic(msgProducer.getDefaultTopic()); - msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); + TenantId tenantId = ownerId.getTenantId(); + EntityId entityId = Optional.ofNullable(ownerId.getEntityId()).orElse(tenantId); + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, entityId).newByTopic(msgProducer.getDefaultTopic()); + msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), statsMsg.build()), null); })); if (!report.isEmpty()) { @@ -139,14 +130,13 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { @Override public void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key, long value) { if (enabled) { - ConcurrentMap statsForKey = stats.get(key); + ConcurrentMap statsForKey = stats.get(key); - statsForKey.computeIfAbsent(tenantId, id -> new AtomicLong()).addAndGet(value); - statsForKey.computeIfAbsent(TenantId.SYS_TENANT_ID, id -> new AtomicLong()).addAndGet(value); + statsForKey.computeIfAbsent(new OwnerId(tenantId), id -> new AtomicLong()).addAndGet(value); + statsForKey.computeIfAbsent(new OwnerId(TenantId.SYS_TENANT_ID), id -> new AtomicLong()).addAndGet(value); if (customerId != null && !customerId.isNullUid()) { - statsForKey.computeIfAbsent(customerId, id -> new AtomicLong()).addAndGet(value); - tenants.putIfAbsent(customerId, tenantId); + statsForKey.computeIfAbsent(new OwnerId(tenantId, customerId), id -> new AtomicLong()).addAndGet(value); } } } @@ -155,4 +145,19 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { public void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key) { report(tenantId, customerId, key, 1); } + + @Data + private static class OwnerId { + private TenantId tenantId; + private EntityId entityId; + + public OwnerId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public OwnerId(TenantId tenantId, EntityId entityId) { + this.tenantId = tenantId; + this.entityId = entityId; + } + } } From 3db338557172537fd7e6bac779f1b08bb418b875 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 20 Apr 2021 11:41:08 +0300 Subject: [PATCH 055/127] Enable/disable usage stats per customer from config --- application/src/main/resources/thingsboard.yml | 1 + .../server/queue/usagestats/DefaultTbApiUsageClient.java | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index a9234a0abb..50c09a9852 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -124,6 +124,7 @@ usage: stats: report: enabled: "${USAGE_STATS_REPORT_ENABLED:true}" + enabled_per_customer: "${USAGE_STATS_REPORT_PER_CUSTOMER_ENABLED:false}" interval: "${USAGE_STATS_REPORT_INTERVAL:10}" check: cycle: "${USAGE_STATS_CHECK_CYCLE:60000}" diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java index 58c858a386..370de259ef 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java @@ -50,6 +50,8 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { @Value("${usage.stats.report.enabled:true}") private boolean enabled; + @Value("${usage.stats.report.enabled_per_customer:false}") + private boolean enabledPerCustomer; @Value("${usage.stats.report.interval:10}") private int interval; @@ -135,7 +137,7 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { statsForKey.computeIfAbsent(new OwnerId(tenantId), id -> new AtomicLong()).addAndGet(value); statsForKey.computeIfAbsent(new OwnerId(TenantId.SYS_TENANT_ID), id -> new AtomicLong()).addAndGet(value); - if (customerId != null && !customerId.isNullUid()) { + if (enabledPerCustomer && customerId != null && !customerId.isNullUid()) { statsForKey.computeIfAbsent(new OwnerId(tenantId, customerId), id -> new AtomicLong()).addAndGet(value); } } From 0ff4e6ac0d86857c5f2492ace8bbdac9976ad31c Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 20 Apr 2021 17:03:46 +0300 Subject: [PATCH 056/127] Improvements to the TbMsg to support CustomerId --- .../server/controller/BaseController.java | 2 +- .../server/controller/DeviceController.java | 4 +- .../edge/rpc/processor/DeviceProcessor.java | 4 +- .../rpc/processor/TelemetryProcessor.java | 12 ++-- .../queue/DefaultTbClusterService.java | 8 +-- .../service/queue/TbClusterService.java | 2 - .../rpc/DefaultTbCoreDeviceRpcService.java | 4 +- .../state/DefaultDeviceStateService.java | 4 +- .../transport/DefaultTransportApiService.java | 4 +- .../thingsboard/server/common/msg/TbMsg.java | 60 +++++++++++++------ common/message/src/main/proto/tbmsg.proto | 3 + .../service/DefaultTransportService.java | 17 +++--- 12 files changed, 73 insertions(+), 51 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 72ee3c1fc2..bc9b91cec4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -926,7 +926,7 @@ public abstract class BaseController { tenantId = ((HasTenantId) entity).getTenantId(); } } - tbClusterService.pushMsgToRuleEngine(tenantId, customerId, entityId, tbMsg, null); + tbClusterService.pushMsgToRuleEngine(tenantId, entityId, tbMsg, null); } catch (Exception e) { log.warn("[{}] Failed to push entity action to rule engine: {}", entityId, actionType, e); } 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 8e6066b970..c0fc578518 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -645,8 +645,8 @@ public class DeviceController extends BaseController { private void pushAssignedFromNotification(Tenant currentTenant, TenantId newTenantId, Device assignedDevice) { String data = entityToStr(assignedDevice); if (data != null) { - TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); - tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getCustomerId(), assignedDevice.getId(), tbMsg, null); + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_ASSIGNED_FROM_TENANT, assignedDevice.getId(), assignedDevice.getCustomerId(), getMetaDataForAssignedFrom(currentTenant), TbMsgDataType.JSON, data); + tbClusterService.pushMsgToRuleEngine(newTenantId, assignedDevice.getId(), tbMsg, null); } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java index 0cc241c743..216e5560d5 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProcessor.java @@ -231,9 +231,9 @@ public class DeviceProcessor extends BaseProcessor { try { DeviceId deviceId = device.getId(); ObjectNode entityNode = mapper.valueToTree(device); - TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, + TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, device.getCustomerId(), getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, mapper.writeValueAsString(entityNode)); - tbClusterService.pushMsgToRuleEngine(tenantId, device.getCustomerId(), deviceId, tbMsg, new TbQueueCallback() { + tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { log.debug("Successfully send ENTITY_CREATED EVENT to rule engine [{}]", device); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java index 6667414815..53ef04fe85 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryProcessor.java @@ -156,8 +156,8 @@ public class TelemetryProcessor extends BaseProcessor { Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); String queueName = defaultQueueAndRuleChain.getKey(); RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); - TbMsg tbMsg = TbMsg.newMsg(queueName, SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, metaData, gson.toJson(json), ruleChainId, null); - tbClusterService.pushMsgToRuleEngine(tenantId, customerId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + TbMsg tbMsg = TbMsg.newMsg(queueName, SessionMsgType.POST_TELEMETRY_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), ruleChainId, null); + tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); @@ -179,8 +179,8 @@ public class TelemetryProcessor extends BaseProcessor { Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); String queueName = defaultQueueAndRuleChain.getKey(); RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); - TbMsg tbMsg = TbMsg.newMsg(queueName, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, metaData, gson.toJson(json), ruleChainId, null); - tbClusterService.pushMsgToRuleEngine(tenantId, customerId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + TbMsg tbMsg = TbMsg.newMsg(queueName, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), entityId, customerId, metaData, gson.toJson(json), ruleChainId, null); + tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); @@ -206,8 +206,8 @@ public class TelemetryProcessor extends BaseProcessor { Pair defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); String queueName = defaultQueueAndRuleChain.getKey(); RuleChainId ruleChainId = defaultQueueAndRuleChain.getValue(); - TbMsg tbMsg = TbMsg.newMsg(queueName, DataConstants.ATTRIBUTES_UPDATED, entityId, metaData, gson.toJson(json), ruleChainId, null); - tbClusterService.pushMsgToRuleEngine(tenantId, customerId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + TbMsg tbMsg = TbMsg.newMsg(queueName, DataConstants.ATTRIBUTES_UPDATED, entityId, customerId, metaData, gson.toJson(json), ruleChainId, null); + tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); 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 e61983b20d..fff648401b 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 @@ -134,12 +134,7 @@ public class DefaultTbClusterService implements TbClusterService { } @Override - public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg msg, TbQueueCallback callback) { - pushMsgToRuleEngine(tenantId, null, entityId, msg, callback); - } - - @Override - public void pushMsgToRuleEngine(TenantId tenantId, CustomerId customerId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) { + public void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg tbMsg, TbQueueCallback callback) { if (tenantId.isNullUid()) { if (entityId.getEntityType().equals(EntityType.TENANT)) { tenantId = new TenantId(entityId.getId()); @@ -154,7 +149,6 @@ public class DefaultTbClusterService implements TbClusterService { tbMsg = transformMsg(tbMsg, deviceProfileCache.get(tenantId, new DeviceProfileId(entityId.getId()))); } } - tbMsg.setCustomerId(customerId); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_RULE_ENGINE, tbMsg.getQueueName(), tenantId, entityId); log.trace("PUSHING msg: {} to:{}", tbMsg, tpi); ToRuleEngineMsg msg = ToRuleEngineMsg.newBuilder() 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 c66ca7a62f..6448a77721 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 @@ -51,8 +51,6 @@ public interface TbClusterService { void pushMsgToRuleEngine(TenantId tenantId, EntityId entityId, TbMsg msg, TbQueueCallback callback); - void pushMsgToRuleEngine(TenantId tenantId, CustomerId customerId, EntityId entityId, TbMsg msg, TbQueueCallback callback); - void pushNotificationToRuleEngine(String targetServiceId, FromDeviceRpcResponse response, TbQueueCallback callback); void pushNotificationToTransport(String targetServiceId, ToTransportMsg response, TbQueueCallback callback); diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java index 08d4143140..85c26d4c4d 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbCoreDeviceRpcService.java @@ -168,8 +168,8 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { entityNode.put("params", msg.getBody().getParams()); try { - TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); - clusterService.pushMsgToRuleEngine(msg.getTenantId(), currentUser.getCustomerId(), msg.getDeviceId(), tbMsg, null); + TbMsg tbMsg = TbMsg.newMsg(DataConstants.RPC_CALL_FROM_SERVER_TO_DEVICE, msg.getDeviceId(), currentUser.getCustomerId(), metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); + clusterService.pushMsgToRuleEngine(msg.getTenantId(), msg.getDeviceId(), tbMsg, null); } catch (JsonProcessingException e) { throw new RuntimeException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 6ccfd255ed..599cdbb730 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -508,8 +508,8 @@ public class DefaultDeviceStateService extends TbApplicationEventListener(tenantId, getCustomerId(sessionInfo), dataPoints, callback)); + CustomerId customerId = getCustomerId(sessionInfo); + MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), new ApiStatsProxyCallback<>(tenantId, customerId, dataPoints, callback)); for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("ts", tsKv.getTs() + ""); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_TELEMETRY_REQUEST, packCallback); - + sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, SessionMsgType.POST_TELEMETRY_REQUEST, packCallback); } } } @@ -433,8 +433,9 @@ public class DefaultTransportService implements TransportService { metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("notifyDevice", "false"); - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, - new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, getCustomerId(sessionInfo), msg.getKvList().size(), callback))); + CustomerId customerId = getCustomerId(sessionInfo); + sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, + new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, customerId, msg.getKvList().size(), callback))); } } @@ -515,7 +516,7 @@ public class DefaultTransportService implements TransportService { metaData.putValue("requestId", Integer.toString(msg.getRequestId())); metaData.putValue("serviceId", serviceInfoProvider.getServiceId()); metaData.putValue("sessionId", sessionId.toString()); - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, + sendToRuleEngine(tenantId, deviceId, getCustomerId(sessionInfo), sessionInfo, json, metaData, SessionMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); toServerRpcPendingMap.put(requestId, new RpcRequestMetadata(sessionId, msg.getRequestId())); @@ -848,7 +849,7 @@ public class DefaultTransportService implements TransportService { ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback); } - private void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, + private void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, CustomerId customerId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); @@ -865,7 +866,7 @@ public class DefaultTransportService implements TransportService { queueName = defaultQueueName != null ? defaultQueueName : ServiceQueue.MAIN; } - TbMsg tbMsg = TbMsg.newMsg(queueName, sessionMsgType.name(), deviceId, metaData, gson.toJson(json), ruleChainId, null); + TbMsg tbMsg = TbMsg.newMsg(queueName, sessionMsgType.name(), deviceId, customerId, metaData, gson.toJson(json), ruleChainId, null); sendToRuleEngine(tenantId, tbMsg, callback); } From 030d80d05a27345db4eef03f3ff51dea45c56d4a Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 21 Apr 2021 10:00:25 +0300 Subject: [PATCH 057/127] CustomerId to TbMsg and SessionInfo --- .../actors/ruleChain/DefaultTbContext.java | 8 ++++- .../server/controller/BaseController.java | 2 +- .../device/DeviceProvisionServiceImpl.java | 4 +-- .../service/queue/TbClusterService.java | 1 - .../transport/DefaultTransportApiService.java | 4 +-- .../thingsboard/server/common/msg/TbMsg.java | 29 ++++++++++++++----- .../server/LwM2mTransportContextServer.java | 2 ++ .../server/LwM2mTransportServiceImpl.java | 2 ++ .../mqtt/session/GatewayDeviceSessionCtx.java | 2 ++ .../rule/engine/api/TbContext.java | 5 ++-- .../TbCopyAttributesToEntityViewNode.java | 2 +- .../rule/engine/action/TbMsgCountNode.java | 10 +++---- .../rule/engine/debug/TbMsgGeneratorNode.java | 8 ++--- .../rule/engine/delay/TbMsgDelayNode.java | 2 +- .../rule/engine/profile/AlarmState.java | 15 +++++----- .../engine/profile/TbDeviceProfileNode.java | 8 ++--- .../rule/engine/rpc/TbSendRPCRequestNode.java | 8 ++--- 17 files changed, 68 insertions(+), 44 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 ae7d8c492e..1ddb99d7b7 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 @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; @@ -269,7 +270,12 @@ class DefaultTbContext implements TbContext { @Override public TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) { - return TbMsg.newMsg(queueName, type, originator, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); + return newMsg(queueName, type, originator, null, metaData, data); + } + + @Override + public TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return TbMsg.newMsg(queueName, type, originator, customerId, metaData, data, nodeCtx.getSelf().getRuleChainId(), nodeCtx.getSelf().getId()); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index bc9b91cec4..6a1ddcff8b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -919,7 +919,7 @@ public abstract class BaseController { entityNode.put("endTs", extractParameter(Long.class, 2, additionalInfo)); } } - TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); + TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, customerId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); TenantId tenantId = user.getTenantId(); if (tenantId.isNullUid()) { if (entity instanceof HasTenantId) { 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 3fea206c3b..4ba4080a74 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 @@ -230,7 +230,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void pushProvisionEventToRuleEngine(ProvisionRequest request, Device device, String type) { try { JsonNode entityNode = JacksonUtil.valueToTree(request); - TbMsg msg = TbMsg.newMsg(type, device.getId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); + TbMsg msg = TbMsg.newMsg(type, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.toString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (IllegalArgumentException e) { log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), type, e); @@ -240,7 +240,7 @@ public class DeviceProvisionServiceImpl implements DeviceProvisionService { private void pushDeviceCreatedEventToRuleEngine(Device device) { try { ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); - TbMsg msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, device.getId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); + TbMsg msg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, device.getId(), device.getCustomerId(), createTbMsgMetaData(device), JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); sendToRuleEngine(device.getTenantId(), msg, null); } catch (JsonProcessingException | IllegalArgumentException e) { log.warn("[{}] Failed to push device action to rule engine: {}", device.getId(), DataConstants.ENTITY_CREATED, e); 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 6448a77721..c5848bed58 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 @@ -22,7 +22,6 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; 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 c7f4db3854..0002fe2dba 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 @@ -412,8 +412,8 @@ public class DefaultTransportApiService implements TransportApiService { return DeviceInfoProto.newBuilder() .setTenantIdMSB(device.getTenantId().getId().getMostSignificantBits()) .setTenantIdLSB(device.getTenantId().getId().getLeastSignificantBits()) - .setCustomerIdMSB(device.getCustomerId().getId().getMostSignificantBits()) - .setCustomerIdLSB(device.getCustomerId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(Optional.ofNullable(device.getCustomerId()).map(customerId -> customerId.getId().getMostSignificantBits()).orElse(0L)) + .setCustomerIdLSB(Optional.ofNullable(device.getCustomerId()).map(customerId -> customerId.getId().getLeastSignificantBits()).orElse(0L)) .setDeviceIdMSB(device.getId().getId().getMostSignificantBits()) .setDeviceIdLSB(device.getId().getId().getLeastSignificantBits()) .setDeviceName(device.getName()) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index e1db40066d..c843fc6db6 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -19,10 +19,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; import lombok.AccessLevel; -import lombok.Builder; import lombok.Data; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -67,8 +67,7 @@ public final class TbMsg implements Serializable { transient private final TbMsgCallback callback; public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { - return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, - metaData.copy(), TbMsgDataType.JSON, data, ruleChainId, ruleNodeId, 0, TbMsgCallback.EMPTY); + return newMsg(queueName, type, originator, null, metaData, data, ruleChainId, ruleNodeId); } public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { @@ -77,13 +76,21 @@ public final class TbMsg implements Serializable { } public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { - return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, TbMsgCallback.EMPTY); + return newMsg(type, originator, null, metaData, data); + } + + public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, TbMsgCallback.EMPTY); } // REALLY NEW MSG public static TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data) { - return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, TbMsgCallback.EMPTY); + return newMsg(queueName, type, originator, null, metaData, data); + } + + public static TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data) { + return new TbMsg(queueName, UUID.randomUUID(), System.currentTimeMillis(), type, originator, customerId, metaData.copy(), TbMsgDataType.JSON, data, null, null, 0, TbMsgCallback.EMPTY); } public static TbMsg newMsg(String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { @@ -91,7 +98,7 @@ public final class TbMsg implements Serializable { } public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, TbMsgDataType dataType, String data) { - return new TbMsg(ServiceQueue.MAIN, UUID.randomUUID(), System.currentTimeMillis(), type, originator, null, metaData.copy(), dataType, data, null, null, 0, TbMsgCallback.EMPTY); + return newMsg(type, originator, null, metaData, dataType, data); } // For Tests only @@ -145,7 +152,15 @@ public final class TbMsg implements Serializable { } this.type = type; this.originator = originator; - this.customerId = (customerId == null || customerId.isNullUid()) ? null : customerId; + if (customerId == null || customerId.isNullUid()) { + if (originator.getEntityType() == EntityType.CUSTOMER) { + this.customerId = (CustomerId) originator; + } else { + this.customerId = null; + } + } else { + this.customerId = customerId; + } this.metaData = metaData; this.dataType = dataType; this.data = data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java index ed0a1d16e6..d3ccf0e86d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java @@ -141,6 +141,8 @@ public class LwM2mTransportContextServer extends TransportContext { .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) + .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB()) + .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB()) .setDeviceName(msg.getDeviceInfo().getDeviceName()) .setDeviceType(msg.getDeviceInfo().getDeviceType()) .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java index 05f386ad1a..4a432ca131 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java @@ -1177,6 +1177,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) + .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB()) + .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB()) .setDeviceName(msg.getDeviceInfo().getDeviceName()) .setDeviceType(msg.getDeviceInfo().getDeviceType()) .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java index a7d5499cfb..e8d9a6b6d4 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewayDeviceSessionCtx.java @@ -45,6 +45,8 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple .setDeviceIdLSB(deviceInfo.getDeviceId().getId().getLeastSignificantBits()) .setTenantIdMSB(deviceInfo.getTenantId().getId().getMostSignificantBits()) .setTenantIdLSB(deviceInfo.getTenantId().getId().getLeastSignificantBits()) + .setCustomerIdMSB(deviceInfo.getCustomerId().getId().getMostSignificantBits()) + .setCustomerIdLSB(deviceInfo.getCustomerId().getId().getLeastSignificantBits()) .setDeviceName(deviceInfo.getDeviceName()) .setDeviceType(deviceInfo.getDeviceType()) .setGwSessionIdMSB(parent.getSessionId().getMostSignificantBits()) 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 e4a71ee33d..7a0e3cce63 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 @@ -16,16 +16,15 @@ 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.ApiUsageRecordKey; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; @@ -144,6 +143,8 @@ public interface TbContext { TbMsg newMsg(String queueName, String type, EntityId originator, TbMsgMetaData metaData, String data); + TbMsg newMsg(String queueName, String type, EntityId originator, CustomerId customerId, TbMsgMetaData metaData, String data); + TbMsg transformMsg(TbMsg origMsg, String type, EntityId originator, TbMsgMetaData metaData, String data); TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java index 8282c74159..60b25574d4 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCopyAttributesToEntityViewNode.java @@ -136,7 +136,7 @@ public class TbCopyAttributesToEntityViewNode implements TbNode { } private void transformAndTellNext(TbContext ctx, TbMsg msg, EntityView entityView) { - ctx.enqueueForTellNext(ctx.newMsg(msg.getQueueName(), msg.getType(), entityView.getId(), msg.getMetaData(), msg.getData()), SUCCESS); + ctx.enqueueForTellNext(ctx.newMsg(msg.getQueueName(), msg.getType(), entityView.getId(), msg.getCustomerId(), msg.getMetaData(), msg.getData()), SUCCESS); } private boolean attributeContainsInEntityView(String scope, String attrKey, EntityView entityView) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java index ca98e104c5..e7d018f65f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbMsgCountNode.java @@ -63,7 +63,7 @@ public class TbMsgCountNode implements TbNode { TbMsgCountNodeConfiguration config = TbNodeUtils.convert(configuration, TbMsgCountNodeConfiguration.class); this.delay = TimeUnit.SECONDS.toMillis(config.getInterval()); this.telemetryPrefix = config.getTelemetryPrefix(); - scheduleTickMsg(ctx); + scheduleTickMsg(ctx, null); } @@ -78,23 +78,23 @@ public class TbMsgCountNode implements TbNode { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("delta", Long.toString(System.currentTimeMillis() - lastScheduledTs + delay)); - TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), ctx.getTenantId(), metaData, gson.toJson(telemetryJson)); + TbMsg tbMsg = TbMsg.newMsg(msg.getQueueName(), SessionMsgType.POST_TELEMETRY_REQUEST.name(), ctx.getTenantId(), msg.getCustomerId(), metaData, gson.toJson(telemetryJson)); ctx.enqueueForTellNext(tbMsg, SUCCESS); - scheduleTickMsg(ctx); + scheduleTickMsg(ctx, tbMsg); } else { messagesProcessed.incrementAndGet(); ctx.ack(msg); } } - private void scheduleTickMsg(TbContext ctx) { + private void scheduleTickMsg(TbContext ctx, TbMsg msg) { long curTs = System.currentTimeMillis(); if (lastScheduledTs == 0L) { lastScheduledTs = curTs; } lastScheduledTs = lastScheduledTs + delay; long curDelay = Math.max(0L, (lastScheduledTs - curTs)); - TbMsg tickMsg = ctx.newMsg(ServiceQueue.MAIN, TB_MSG_COUNT_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), ""); + TbMsg tickMsg = ctx.newMsg(ServiceQueue.MAIN, TB_MSG_COUNT_NODE_MSG, ctx.getSelfId(), msg != null ? msg.getCustomerId() : null, new TbMsgMetaData(), ""); nextTickId = tickMsg.getId(); ctx.tellSelf(tickMsg, curDelay); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index fc6ee959e0..b5a47b4a0d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -100,7 +100,7 @@ public class TbMsgGeneratorNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { if (initialized && msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { - withCallback(generate(ctx), + withCallback(generate(ctx, msg), m -> { if (initialized && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.enqueueForTellNext(m, SUCCESS); @@ -130,16 +130,16 @@ public class TbMsgGeneratorNode implements TbNode { ctx.tellSelf(tickMsg, curDelay); } - private ListenableFuture generate(TbContext ctx) { + private ListenableFuture generate(TbContext ctx, TbMsg msg) { return ctx.getJsExecutor().executeAsync(() -> { if (prevMsg == null) { - prevMsg = ctx.newMsg(ServiceQueue.MAIN, "", originatorId, new TbMsgMetaData(), "{}"); + prevMsg = ctx.newMsg(ServiceQueue.MAIN, "", originatorId, msg.getCustomerId(), new TbMsgMetaData(), "{}"); } if (initialized) { ctx.logJsEvalRequest(); TbMsg generated = jsEngine.executeGenerate(prevMsg); ctx.logJsEvalResponse(); - prevMsg = ctx.newMsg(ServiceQueue.MAIN, generated.getType(), originatorId, generated.getMetaData(), generated.getData()); + prevMsg = ctx.newMsg(ServiceQueue.MAIN, generated.getType(), originatorId, msg.getCustomerId(), generated.getMetaData(), generated.getData()); } return prevMsg; }); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java index 730cb734be..4a3113baca 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java @@ -70,7 +70,7 @@ public class TbMsgDelayNode implements TbNode { } else { if (pendingMsgs.size() < config.getMaxPendingMsgs()) { pendingMsgs.put(msg.getId(), msg); - TbMsg tickMsg = ctx.newMsg(ServiceQueue.MAIN, TB_MSG_DELAY_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), msg.getId().toString()); + TbMsg tickMsg = ctx.newMsg(ServiceQueue.MAIN, TB_MSG_DELAY_NODE_MSG, ctx.getSelfId(), msg.getCustomerId(), new TbMsgMetaData(), msg.getId().toString()); ctx.tellSelf(tickMsg, getDelay(msg)); ctx.ack(msg); } else { 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 ceee362fc8..445b9653a2 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 @@ -74,15 +74,15 @@ class AlarmState { lastMsgMetaData = msg.getMetaData(); lastMsgQueueName = msg.getQueueName(); this.dataSnapshot = data; - return createOrClearAlarms(ctx, data, update, AlarmRuleState::eval); + return createOrClearAlarms(ctx, msg, data, update, AlarmRuleState::eval); } public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException { initCurrentAlarm(ctx); - return createOrClearAlarms(ctx, ts, null, AlarmRuleState::eval); + return createOrClearAlarms(ctx, null, ts, null, AlarmRuleState::eval); } - public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { + public boolean createOrClearAlarms(TbContext ctx, TbMsg msg, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; AlarmRuleState resultState = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); @@ -103,7 +103,7 @@ class AlarmState { if (resultState != null) { TbAlarmResult result = calculateAlarmResult(ctx, resultState); if (result != null) { - pushMsg(ctx, result, resultState); + pushMsg(ctx, msg, result, resultState); } stateUpdate = clearAlarmState(stateUpdate, clearState); } else if (currentAlarm != null && clearState != null) { @@ -122,7 +122,7 @@ class AlarmState { ); DonAsynchron.withCallback(alarmClearOperationResult, result -> { - pushMsg(ctx, new TbAlarmResult(false, false, true, result.getAlarm()), clearState); + pushMsg(ctx, msg, new TbAlarmResult(false, false, true, result.getAlarm()), clearState); }, throwable -> { throw new RuntimeException(throwable); @@ -165,7 +165,7 @@ class AlarmState { } } - public void pushMsg(TbContext ctx, TbAlarmResult alarmResult, AlarmRuleState ruleState) { + public void pushMsg(TbContext ctx, TbMsg msg, TbAlarmResult alarmResult, AlarmRuleState ruleState) { JsonNode jsonNodes = JacksonUtil.valueToTree(alarmResult.getAlarm()); String data = jsonNodes.toString(); TbMsgMetaData metaData = lastMsgMetaData != null ? lastMsgMetaData.copy() : new TbMsgMetaData(); @@ -185,7 +185,8 @@ class AlarmState { metaData.putValue(DataConstants.IS_CLEARED_ALARM, Boolean.TRUE.toString()); } setAlarmConditionMetadata(ruleState, metaData); - TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : ServiceQueue.MAIN, "ALARM", originator, metaData, data); + TbMsg newMsg = ctx.newMsg(lastMsgQueueName != null ? lastMsgQueueName : ServiceQueue.MAIN, "ALARM", + originator, msg != null ? msg.getCustomerId() : null, metaData, data); ctx.tellNext(newMsg, relationType); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java index a733fd104a..ebf72d6b3a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNode.java @@ -74,7 +74,7 @@ public class TbDeviceProfileNode implements TbNode { this.config = TbNodeUtils.convert(configuration, TbDeviceProfileNodeConfiguration.class); this.cache = ctx.getDeviceProfileCache(); this.ctx = ctx; - scheduleAlarmHarvesting(ctx); + scheduleAlarmHarvesting(ctx, null); ctx.addDeviceProfileListeners(this::onProfileUpdate, this::onDeviceUpdate); if (config.isFetchAlarmRulesStateOnStart()) { log.info("[{}] Fetching alarm rule state", ctx.getSelfId()); @@ -108,7 +108,7 @@ public class TbDeviceProfileNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { EntityType originatorType = msg.getOriginator().getEntityType(); if (msg.getType().equals(PERIODIC_MSG_TYPE)) { - scheduleAlarmHarvesting(ctx); + scheduleAlarmHarvesting(ctx, msg); harvestAlarms(ctx, System.currentTimeMillis()); } else if (msg.getType().equals(PROFILE_UPDATE_MSG_TYPE)) { updateProfile(ctx, new DeviceProfileId(UUID.fromString(msg.getData()))); @@ -168,8 +168,8 @@ public class TbDeviceProfileNode implements TbNode { return deviceState; } - protected void scheduleAlarmHarvesting(TbContext ctx) { - TbMsg periodicCheck = TbMsg.newMsg(PERIODIC_MSG_TYPE, ctx.getTenantId(), TbMsgMetaData.EMPTY, "{}"); + protected void scheduleAlarmHarvesting(TbContext ctx, TbMsg msg) { + TbMsg periodicCheck = TbMsg.newMsg(PERIODIC_MSG_TYPE, ctx.getTenantId(), msg != null ? msg.getCustomerId() : null, TbMsgMetaData.EMPTY, "{}"); ctx.tellSelf(periodicCheck, TimeUnit.MINUTES.toMillis(1)); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java index 07952359a1..31bd913f47 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCRequestNode.java @@ -16,10 +16,6 @@ package org.thingsboard.rule.engine.rpc; import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; @@ -116,10 +112,10 @@ public class TbSendRPCRequestNode implements TbNode { ctx.getRpcService().sendRpcRequestToDevice(request, ruleEngineDeviceRpcResponse -> { if (!ruleEngineDeviceRpcResponse.getError().isPresent()) { - TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse("{}")); + TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), ruleEngineDeviceRpcResponse.getResponse().orElse("{}")); ctx.enqueueForTellNext(next, TbRelationTypes.SUCCESS); } else { - TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); + TbMsg next = ctx.newMsg(msg.getQueueName(), msg.getType(), msg.getOriginator(), msg.getCustomerId(), msg.getMetaData(), wrap("error", ruleEngineDeviceRpcResponse.getError().get().name())); ctx.tellFailure(next, new RuntimeException(ruleEngineDeviceRpcResponse.getError().get().name())); } }); From 6b008e52cb2b70bc46f6229a7275c494f365d8be Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 21 Apr 2021 10:39:14 +0300 Subject: [PATCH 058/127] Refactor --- .../thingsboard/server/common/msg/TbMsg.java | 2 +- .../profile/TbDeviceProfileNodeTest.java | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index c843fc6db6..1207f4d634 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -153,7 +153,7 @@ public final class TbMsg implements Serializable { this.type = type; this.originator = originator; if (customerId == null || customerId.isNullUid()) { - if (originator.getEntityType() == EntityType.CUSTOMER) { + if (originator != null && originator.getEntityType() == EntityType.CUSTOMER) { this.customerId = (CustomerId) originator; } else { this.customerId = null; diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java index 6a30eb63ab..faf934cd5e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java @@ -183,7 +183,7 @@ public class TbDeviceProfileNodeTest { Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg); ObjectNode data = mapper.createObjectNode(); data.put("temperature", 42); @@ -195,7 +195,7 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); TbMsg theMsg2 = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "2"); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg2); + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())).thenReturn(theMsg2); TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), @@ -274,7 +274,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(attrListListenableFuture); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = mapper.createObjectNode(); @@ -361,7 +361,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(attrListListenableFuture); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = mapper.createObjectNode(); @@ -430,7 +430,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(listListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = mapper.createObjectNode(); @@ -510,7 +510,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = mapper.createObjectNode(); @@ -584,7 +584,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = mapper.createObjectNode(); @@ -668,7 +668,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = mapper.createObjectNode(); @@ -754,7 +754,7 @@ public class TbDeviceProfileNodeTest { .thenReturn(optionalListenableFutureWithLess); TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); - Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.anyString())) + Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString())) .thenReturn(theMsg); ObjectNode data = mapper.createObjectNode(); From 8c9213cff5fc56e0c9143f64a5ee0ba0d1e3262d Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 21 Apr 2021 16:07:48 +0300 Subject: [PATCH 059/127] Provide querying by customer for API usage states --- .../data/query/ApiUsageStateFilter.java | 5 ++++- .../query/DefaultEntityQueryRepository.java | 22 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java index f1008feb4f..f2f90472c0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java @@ -16,10 +16,13 @@ package org.thingsboard.server.common.data.query; import lombok.Data; -import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.CustomerId; @Data public class ApiUsageStateFilter implements EntityFilter { + + private CustomerId customerId; + @Override public EntityFilterType getType() { return EntityFilterType.API_USAGE_STATE; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index ac4d2b9553..9c7e8de01d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.query.ApiUsageStateFilter; import org.thingsboard.server.common.data.query.AssetSearchQueryFilter; import org.thingsboard.server.common.data.query.AssetTypeFilter; import org.thingsboard.server.common.data.query.DeviceSearchQueryFilter; @@ -219,8 +220,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select additional_info from edge where id = entity_id)" + " END as additional_info"; - private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, '13814000-1dd2-11b2-8080-808080808080'::uuid as customer_id, " + - "(select title from tenant where id = aus.tenant_id) as name from api_usage_state as aus)"; + private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, aus.entity_id, " + + "coalesce((select title from tenant where id = aus.entity_id), (select title from customer where id = aus.entity_id)) as name " + + "from api_usage_state as aus)"; static { entityTableMap.put(EntityType.ASSET, "asset"); @@ -466,6 +468,22 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case ENTITY_VIEW_SEARCH_QUERY: case EDGE_SEARCH_QUERY: return this.defaultPermissionQuery(ctx); + case API_USAGE_STATE: + CustomerId filterCustomerId = ((ApiUsageStateFilter) entityFilter).getCustomerId(); + if (ctx.getCustomerId() != null && !ctx.getCustomerId().isNullUid()) { + if (filterCustomerId != null && !filterCustomerId.equals(ctx.getCustomerId())) { + throw new SecurityException("Customer is not allowed to query other customer's data"); + } + filterCustomerId = ctx.getCustomerId(); + } + + ctx.addUuidParameter("permissions_tenant_id", ctx.getTenantId().getId()); + if (filterCustomerId != null) { + ctx.addUuidParameter("permissions_customer_id", filterCustomerId.getId()); + return "e.tenant_id=:permissions_tenant_id and e.entity_id=:permissions_customer_id"; + } else { + return "e.tenant_id=:permissions_tenant_id and e.entity_id=:permissions_tenant_id"; + } default: if (ctx.getEntityType() == EntityType.TENANT) { ctx.addUuidParameter("permissions_tenant_id", ctx.getTenantId().getId()); From 5465703d2d8d8ba57b4a7db706fe4a72e7b70c8a Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 22 Apr 2021 11:48:52 +0300 Subject: [PATCH 060/127] Handle tenant and customer deletion for TbApiUsageStateService --- .../server/controller/CustomerController.java | 2 ++ .../DefaultTbApiUsageStateService.java | 20 +++++++++++++++++-- .../apiusage/TbApiUsageStateService.java | 5 +++++ .../processing/AbstractConsumerService.java | 12 +++++++++-- .../dao/usagerecord/ApiUsageStateService.java | 2 ++ .../dao/customer/CustomerServiceImpl.java | 5 +++++ .../usagerecord/ApiUsageStateRepository.java | 4 ++++ .../sql/usagerecord/JpaApiUsageStateDao.java | 5 +++++ .../dao/usagerecord/ApiUsageStateDao.java | 2 ++ .../usagerecord/ApiUsageStateServiceImpl.java | 7 +++++++ 10 files changed, 60 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index 484b15f076..9eb1dc044a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -148,6 +149,7 @@ public class CustomerController extends BaseController { ActionType.DELETED, null, strCustomerId); sendDeleteNotificationMsg(getTenantId(), customerId, relatedEdgeIds); + tbClusterService.onEntityStateChange(getTenantId(), customerId, ComponentLifecycleEvent.DELETED); } catch (Exception e) { logEntityAction(emptyId(EntityType.CUSTOMER), 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 08e81f28d7..9c7915d4c4 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 @@ -111,11 +111,13 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener myUsageStates = new ConcurrentHashMap<>(); - // Tenants that should be processed on other servers + // Entities that should be processed on other servers private final Map otherUsageStates = new ConcurrentHashMap<>(); + private final Set deletedEntities = Collections.newSetFromMap(new ConcurrentHashMap<>()); + @Value("${usage.stats.report.enabled:true}") private boolean enabled; @@ -173,6 +175,8 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener values) { + if (deletedEntities.contains(entityId)) return; + BaseApiUsageState usageState; List updatedEntries; Map result; @@ -321,6 +325,18 @@ public class DefaultTbApiUsageStateService extends TbApplicationEventListener result) { log.info("[{}] Detected update of the API state for {}: {}", state.getEntityId(), state.getEntityType(), result); apiUsageStateService.update(state.getApiUsageState()); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 117607ae9f..27755b9b39 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.apiusage; import org.springframework.context.ApplicationListener; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; @@ -34,5 +35,9 @@ public interface TbApiUsageStateService extends ApplicationListener> nfConsumer; public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, - TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, TbApiUsageStateService apiUsageStateService, TbQueueConsumer> nfConsumer) { + TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, + TbApiUsageStateService apiUsageStateService, TbQueueConsumer> nfConsumer) { this.actorContext = actorContext; this.encodingService = encodingService; this.tenantProfileCache = tenantProfileCache; @@ -166,6 +168,8 @@ public abstract class AbstractConsumerService { * @param tenantId the tenantId */ void deleteApiUsageStateByTenantId(TenantId tenantId); + + void deleteApiUsageStateByEntityId(EntityId entityId); } 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 9e23cd843a..7f928d6c41 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 @@ -69,6 +69,13 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageStateDao.deleteApiUsageStateByTenantId(tenantId); } + @Override + public void deleteApiUsageStateByEntityId(EntityId entityId) { + log.trace("Executing deleteApiUsageStateByEntityId [{}]", entityId); + validateId(entityId.getId(), "Invalid entity id"); + apiUsageStateDao.deleteApiUsageStateByEntityId(entityId); + } + @Override public ApiUsageState createDefaultApiUsageState(TenantId tenantId, EntityId entityId) { entityId = Objects.requireNonNullElse(entityId, tenantId); From c9439b3976ad98a77a09ac09c0eccc654a4ba86c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Apr 2021 10:17:51 +0300 Subject: [PATCH 061/127] created created firmware queue --- .../firmware/DefaultFirmwareStateService.java | 88 +++++++++++++++++-- .../firmware/FirmwareStateService.java | 3 + .../firmware/FirmwareUpdateStatus.java | 5 ++ .../queue/DefaultTbCoreConsumerService.java | 76 +++++++++++++++- .../transport/DefaultTransportApiService.java | 2 + .../src/main/resources/thingsboard.yml | 5 ++ .../server/dao/device/DeviceService.java | 2 + .../server/common/data/DataConstants.java | 2 +- .../queue/kafka/TbKafkaTopicConfigs.java | 5 ++ .../provider/AwsSqsMonolithQueueFactory.java | 11 +++ .../provider/AwsSqsTbCoreQueueFactory.java | 20 ++++- .../InMemoryMonolithQueueFactory.java | 10 +++ .../provider/KafkaMonolithQueueFactory.java | 26 ++++++ .../provider/KafkaTbCoreQueueFactory.java | 26 ++++++ .../provider/PubSubMonolithQueueFactory.java | 12 +++ .../provider/PubSubTbCoreQueueFactory.java | 12 +++ .../RabbitMqMonolithQueueFactory.java | 12 +++ .../provider/RabbitMqTbCoreQueueFactory.java | 12 +++ .../ServiceBusMonolithQueueFactory.java | 12 +++ .../ServiceBusTbCoreQueueFactory.java | 12 +++ .../queue/provider/TbCoreQueueFactory.java | 15 ++++ .../queue/settings/TbQueueCoreSettings.java | 3 + common/queue/src/main/proto/queue.proto | 16 +++- .../transport/http/DeviceApiController.java | 14 ++- .../server/dao/device/DeviceDao.java | 2 + .../server/dao/device/DeviceServiceImpl.java | 9 ++ .../dao/sql/device/DeviceRepository.java | 9 ++ .../server/dao/sql/device/JpaDeviceDao.java | 10 +++ .../src/main/resources/tb-coap-transport.yml | 1 - .../src/main/resources/tb-http-transport.yml | 1 - .../src/main/resources/tb-lwm2m-transport.yml | 1 - .../src/main/resources/tb-mqtt-transport.yml | 1 - 32 files changed, 409 insertions(+), 26 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index 0c0b4d15e1..d791b882ee 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -34,16 +34,23 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.firmware.FirmwareService; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import javax.annotation.Nullable; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; -import java.util.Objects; +import java.util.UUID; import java.util.function.Consumer; import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM; @@ -61,12 +68,18 @@ public class DefaultFirmwareStateService implements FirmwareStateService { private final DeviceService deviceService; private final DeviceProfileService deviceProfileService; private final RuleEngineTelemetryService telemetryService; + private final TbQueueProducer> fwStateMsgProducer; - public DefaultFirmwareStateService(FirmwareService firmwareService, DeviceService deviceService, DeviceProfileService deviceProfileService, RuleEngineTelemetryService telemetryService) { + public DefaultFirmwareStateService(FirmwareService firmwareService, + DeviceService deviceService, + DeviceProfileService deviceProfileService, + RuleEngineTelemetryService telemetryService, + TbCoreQueueFactory coreQueueFactory) { this.firmwareService = firmwareService; this.deviceService = deviceService; this.deviceProfileService = deviceProfileService; this.telemetryService = telemetryService; + this.fwStateMsgProducer = coreQueueFactory.createToFirmwareStateServiceMsgProducer(); } @Override @@ -85,7 +98,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } if (!newFirmwareId.equals(oldFirmwareId)) { // Device was updated and new firmware is different from previous firmware. - update(device, firmwareService.findFirmwareById(device.getTenantId(), newFirmwareId), System.currentTimeMillis()); + send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis()); } } else { // Device was updated and new firmware is not set. @@ -93,7 +106,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } } else if (newFirmwareId != null) { // Device was created and firmware is defined. - update(device, firmwareService.findFirmwareById(device.getTenantId(), newFirmwareId), System.currentTimeMillis()); + send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis()); } } @@ -103,9 +116,8 @@ public class DefaultFirmwareStateService implements FirmwareStateService { Consumer updateConsumer; if (deviceProfile.getFirmwareId() != null) { - Firmware firmware = firmwareService.findFirmwareById(tenantId, deviceProfile.getFirmwareId()); long ts = System.currentTimeMillis(); - updateConsumer = d -> update(d, firmware, ts); + updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts); } else { updateConsumer = this::remove; } @@ -113,10 +125,9 @@ public class DefaultFirmwareStateService implements FirmwareStateService { PageLink pageLink = new PageLink(100); PageData pageData; do { - //TODO: create a query which will return devices without firmware - pageData = deviceService.findDevicesByTenantIdAndType(tenantId, deviceProfile.getName(), pageLink); + pageData = deviceService.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId, deviceProfile.getName(), pageLink); - pageData.getData().stream().filter(d -> d.getFirmwareId() == null).forEach(updateConsumer); + pageData.getData().forEach(updateConsumer); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -124,6 +135,64 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } while (pageData.hasNext()); } + @Override + public boolean process(ToFirmwareStateServiceMsg msg) { + boolean isSuccess = false; + FirmwareId targetFirmwareId = new FirmwareId(new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB())); + DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); + TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); + long ts = msg.getTs(); + + Device device = deviceService.findDeviceById(tenantId, deviceId); + if (device == null) { + log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId); + } else { + FirmwareId currentFirmwareId = device.getFirmwareId(); + + if (currentFirmwareId == null) { + currentFirmwareId = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId()).getFirmwareId(); + } + + if (targetFirmwareId.equals(currentFirmwareId)) { + update(device, firmwareService.findFirmwareById(device.getTenantId(), targetFirmwareId), ts); + isSuccess = true; + } else { + log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetFirmwareId, currentFirmwareId); + } + } + return isSuccess; + } + + private void send(TenantId tenantId, DeviceId deviceId, FirmwareId firmwareId, long ts) { + ToFirmwareStateServiceMsg msg = ToFirmwareStateServiceMsg.newBuilder() + .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) + .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits()) + .setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()) + .setTs(ts) + .build(); + + TopicPartitionInfo tpi = new TopicPartitionInfo(fwStateMsgProducer.getDefaultTopic(), null, null, false); + fwStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); + + BasicTsKvEntry status = new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name())); + + telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { + @Override + public void onSuccess(@Nullable Void tmp) { + log.trace("[{}] Success save firmware status!", deviceId); + } + + @Override + public void onFailure(Throwable t) { + log.error("[{}] Failed to save firmware status!", deviceId, t); + } + }); + } + + private void update(Device device, Firmware firmware, long ts) { TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); @@ -131,6 +200,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { List telemetry = new ArrayList<>(); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.INITIATED.name()))); telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java index 3f84e5d0fb..ac51b0d95e 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.firmware; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; public interface FirmwareStateService { @@ -24,4 +25,6 @@ public interface FirmwareStateService { void update(DeviceProfile deviceProfile); + boolean process(ToFirmwareStateServiceMsg msg); + } diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java new file mode 100644 index 0000000000..d62265840a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java @@ -0,0 +1,5 @@ +package org.thingsboard.server.service.firmware; + +public enum FirmwareUpdateStatus { + QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED +} 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 71dd1a2e07..657320170f 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 @@ -24,6 +24,7 @@ import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.actors.ActorSystemContext; @@ -35,7 +36,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; -import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.EdgeNotificationMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; @@ -49,6 +50,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseP import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.queue.TbQueueConsumer; @@ -58,8 +60,8 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; +import org.thingsboard.server.service.firmware.FirmwareStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; @@ -97,6 +99,11 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> mainConsumer; private final DeviceStateService stateService; private final TbApiUsageStateService statsService; @@ -104,11 +111,15 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> usageStatsConsumer; + private final TbQueueConsumer> firmwareStatesConsumer; protected volatile ExecutorService usageStatsExecutor; + private volatile ExecutorService firmwareStatesExecutor; + public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext, DeviceStateService stateService, @@ -121,10 +132,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService tpi.newByTopic(usageStatsConsumer.getTopic())) .collect(Collectors.toSet())); } + this.firmwareStatesConsumer.subscribe(); } @Override @@ -336,10 +356,57 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { + while (!stopped) { + try { + List> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration()); + if (msgs.isEmpty()) { + Thread.sleep(maxProcessingTimeoutPerRecord); + continue; + } + long timeToSleep = maxProcessingTimeoutPerRecord; + for (TbProtoQueueMsg msg : msgs) { + try { + long startTime = System.currentTimeMillis(); + boolean isSuccessUpdate = handleFirmwareUpdates(msg); + long endTime = System.currentTimeMillis(); + long spentTime = endTime - startTime; + timeToSleep = timeToSleep - spentTime; + if (isSuccessUpdate && timeToSleep > 0) { + log.debug("Spent time per record is: [{}]!", spentTime); + Thread.sleep(timeToSleep); + timeToSleep = maxProcessingTimeoutPerRecord; + } + } catch (Throwable e) { + log.warn("Failed to process firmware update msg: {}", msg, e); + } + } + firmwareStatesConsumer.commit(); + } catch (Exception e) { + if (!stopped) { + log.warn("Failed to obtain usage stats from queue.", e); + try { + Thread.sleep(getNotificationPollDuration()); + } catch (InterruptedException e2) { + log.trace("Failed to wait until the server has capacity to handle new firmware updates", e2); + } + } + } + } + log.info("TB Firmware States Consumer stopped."); + }); + } + private void handleUsageStats(TbProtoQueueMsg msg, TbCallback callback) { statsService.process(msg, callback); } + private boolean handleFirmwareUpdates(TbProtoQueueMsg msg) { + return firmwareStateService.process(msg.getValue()); + } + private void forwardToCoreRpcService(FromDeviceRPCResponseProto proto, TbCallback callback) { RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()) @@ -448,6 +515,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); + PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(TenantId tenantId, String type, PageLink pageLink); + PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); PageData findDeviceInfosByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index e244873556..d53e992db8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -99,7 +99,7 @@ public class DataConstants { public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version"; public static final String TARGET_FIRMWARE_TITLE = "target_fw_title"; public static final String TARGET_FIRMWARE_VERSION = "target_fw_version"; - public static final String CURRENT_FIRMWARE_STATE = "cur_fw_state"; + public static final String FIRMWARE_STATE = "fw_state"; //attributes //telemetry diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java index 0dc48eac82..16ec1be164 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java @@ -37,6 +37,8 @@ public class TbKafkaTopicConfigs { private String notificationsProperties; @Value("${queue.kafka.topic-properties.js-executor}") private String jsExecutorProperties; + @Value("${queue.kafka.topic-properties.fw-updates}") + private String fwUpdatesProperties; @Getter private Map coreConfigs; @@ -48,6 +50,8 @@ public class TbKafkaTopicConfigs { private Map notificationsConfigs; @Getter private Map jsExecutorConfigs; + @Getter + private Map fwUpdatesConfigs; @PostConstruct private void init() { @@ -56,6 +60,7 @@ public class TbKafkaTopicConfigs { transportApiConfigs = getConfigs(transportApiProperties); notificationsConfigs = getConfigs(notificationsProperties); jsExecutorConfigs = getConfigs(jsExecutorProperties); + fwUpdatesConfigs = getConfigs(fwUpdatesProperties); } private Map getConfigs(String properties) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java index 2bf396bb67..c2f4f4f750 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java @@ -186,6 +186,17 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getFirmwareTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getFirmwareTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java index e20770a931..0267cdecb8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java @@ -21,12 +21,13 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -165,14 +166,25 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); } @Override - public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getUsageStatsTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getFirmwareTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getFirmwareTopic()); } @PreDestroy diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java index da3718c0f2..8806176f9f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java @@ -130,6 +130,16 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return new InMemoryTbQueueConsumer<>(coreSettings.getUsageStatsTopic()); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new InMemoryTbQueueConsumer<>(coreSettings.getFirmwareTopic()); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new InMemoryTbQueueProducer<>(coreSettings.getFirmwareTopic()); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { return new InMemoryTbQueueProducer<>(coreSettings.getUsageStatsTopic()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index e07f3e43d3..a526331116 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -73,6 +74,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi private final TbQueueAdmin jsExecutorAdmin; private final TbQueueAdmin transportApiAdmin; private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin fwUpdatesAdmin; public KafkaMonolithQueueFactory(PartitionService partitionService, TbKafkaSettings kafkaSettings, TbServiceInfoProvider serviceInfoProvider, @@ -98,6 +100,7 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi this.jsExecutorAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorConfigs()); this.transportApiAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiConfigs()); this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs()); + this.fwUpdatesAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getFwUpdatesConfigs()); } @Override @@ -273,6 +276,29 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi return consumerBuilder.build(); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(coreSettings.getFirmwareTopic()); + consumerBuilder.clientId("monolith-fw-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("monolith-fw-consumer"); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(fwUpdatesAdmin); + consumerBuilder.statsService(consumerStatsService); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("monolith-fw-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getFirmwareTopic()); + requestBuilder.admin(fwUpdatesAdmin); + return requestBuilder.build(); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index 24e80adec5..5f64e42ffd 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -70,6 +71,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { private final TbQueueAdmin jsExecutorAdmin; private final TbQueueAdmin transportApiAdmin; private final TbQueueAdmin notificationAdmin; + private final TbQueueAdmin fwUpdatesAdmin; public KafkaTbCoreQueueFactory(PartitionService partitionService, TbKafkaSettings kafkaSettings, TbServiceInfoProvider serviceInfoProvider, @@ -93,6 +95,7 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { this.jsExecutorAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getJsExecutorConfigs()); this.transportApiAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getTransportApiConfigs()); this.notificationAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getNotificationsConfigs()); + this.fwUpdatesAdmin = new TbKafkaAdmin(kafkaSettings, kafkaTopicConfigs.getFwUpdatesConfigs()); } @Override @@ -241,6 +244,29 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { return consumerBuilder.build(); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(coreSettings.getFirmwareTopic()); + consumerBuilder.clientId("tb-core-fw-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("tb-core-fw-consumer"); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(fwUpdatesAdmin); + consumerBuilder.statsService(consumerStatsService); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-core-fw-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getFirmwareTopic()); + requestBuilder.admin(fwUpdatesAdmin); + return requestBuilder.build(); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java index 999a9c4259..3504c6aef2 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java @@ -22,6 +22,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; +import org.thingsboard.server.gen.transport.TransportProtos.*; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -190,6 +191,17 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic()); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java index 2a63a24282..a9ebf5a4de 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -164,6 +165,17 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getFirmwareTopic()); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java index 1fa0363d28..cd6043cdde 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java @@ -24,6 +24,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -188,6 +189,17 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic()); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java index 5819e0e65a..8b5ad00de4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -170,6 +171,17 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getFirmwareTopic()); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java index df4f713b23..b569d13742 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -187,6 +188,17 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic()); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java index 4ee46f2422..17e6eb9a27 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -170,6 +171,17 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueConsumer> createToFirmwareStateServiceMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToFirmwareStateServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToFirmwareStateServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getFirmwareTopic()); + } + @Override public TbQueueProducer> createToUsageStatsServiceMsgProducer() { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java index 5bb9de9564..be5ca967bf 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java @@ -16,6 +16,7 @@ package org.thingsboard.server.queue.provider; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -85,6 +86,20 @@ public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory { */ TbQueueConsumer> createToUsageStatsServiceMsgConsumer(); + /** + * Used to consume messages about firmware update notifications by TB Core Service + * + * @return + */ + TbQueueConsumer> createToFirmwareStateServiceMsgConsumer(); + + /** + * Used to consume messages about firmware update notifications by TB Core Service + * + * @return + */ + TbQueueProducer> createToFirmwareStateServiceMsgProducer(); + /** * Used to consume high priority messages by TB Core Service * diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java index 5f9880d2c1..d194fdb4ea 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java @@ -26,6 +26,9 @@ public class TbQueueCoreSettings { @Value("${queue.core.topic}") private String topic; + @Value("${queue.core.firmware.topic:tb_firmware}") + private String firmwareTopic; + @Value("${queue.core.usage-stats-topic:tb_usage_stats}") private String usageStatsTopic; diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 6335670f07..6f1f44b765 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -359,8 +359,10 @@ message GetFirmwareResponseMsg { ResponseStatus responseStatus = 1; int64 firmwareIdMSB = 2; int64 firmwareIdLSB = 3; - string contentType = 4; - string fileName = 5; + string title = 4; + string version = 5; + string contentType = 6; + string fileName = 7; } //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. @@ -657,3 +659,13 @@ message ToUsageStatsServiceMsg { int64 entityIdLSB = 4; repeated UsageStatsKVProto values = 5; } + +message ToFirmwareStateServiceMsg { + int64 ts = 1; + int64 tenantIdMSB = 2; + int64 tenantIdLSB = 3; + int64 deviceIdMSB = 4; + int64 deviceIdLSB = 5; + int64 firmwareIdMSB = 6; + int64 firmwareIdLSB = 7; +} diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index 5c10202d00..dc3b980f88 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -207,6 +207,8 @@ public class DeviceApiController { @RequestMapping(value = "/{deviceToken}/firmware", method = RequestMethod.GET) public DeferredResult getFirmware(@PathVariable("deviceToken") String deviceToken, + @RequestParam(value = "title") String title, + @RequestParam(value = "version") String version, @RequestParam(value = "chunkSize", required = false, defaultValue = "0") int chunkSize, @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) { DeferredResult responseWriter = new DeferredResult<>(); @@ -217,7 +219,7 @@ public class DeviceApiController { .setTenantIdLSB(sessionInfo.getTenantIdLSB()) .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build(); - transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, chunkSize, chunk)); + transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, title, version, chunkSize, chunk)); })); return responseWriter; } @@ -278,11 +280,15 @@ public class DeviceApiController { private class GetFirmwareCallback implements TransportServiceCallback { private final DeferredResult responseWriter; + private final String title; + private final String version; private final int chuckSize; private final int chuck; - GetFirmwareCallback(DeferredResult responseWriter, int chuckSize, int chuck) { + GetFirmwareCallback(DeferredResult responseWriter, String title, String version, int chuckSize, int chuck) { this.responseWriter = responseWriter; + this.title = title; + this.version = version; this.chuckSize = chuckSize; this.chuck = chuck; } @@ -291,7 +297,7 @@ public class DeviceApiController { public void onSuccess(TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg) { if (!TransportProtos.ResponseStatus.SUCCESS.equals(firmwareResponseMsg.getResponseStatus())) { responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_FOUND)); - } else { + } else if (title.equals(firmwareResponseMsg.getTitle()) && version.equals(firmwareResponseMsg.getVersion())) { String firmwareId = new UUID(firmwareResponseMsg.getFirmwareIdMSB(), firmwareResponseMsg.getFirmwareIdLSB()).toString(); ByteArrayResource resource = new ByteArrayResource(transportContext.getFirmwareCacheReader().get(firmwareId, chuckSize, chuck)); ResponseEntity response = ResponseEntity.ok() @@ -301,6 +307,8 @@ public class DeviceApiController { .contentType(parseMediaType(firmwareResponseMsg.getContentType())) .body(resource); responseWriter.setResult(response); + } else { + responseWriter.setResult(new ResponseEntity<>(HttpStatus.BAD_REQUEST)); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 784eb4e229..0a61ce554e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -81,6 +81,8 @@ public interface DeviceDao extends Dao, TenantEntityDao { */ PageData findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); + PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(UUID tenantId, String type, PageLink pageLink); + /** * Find device infos by tenantId, type and page link. * diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 73eff3b5e6..73dadef589 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -354,6 +354,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDevicesByTenantIdAndType(tenantId.getId(), type, pageLink); } + @Override + public PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(TenantId tenantId, String type, PageLink pageLink) { + log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateString(type, "Incorrect type " + type); + validatePageLink(pageLink); + return deviceDao.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId.getId(), type, pageLink); + } + @Override public PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { log.trace("Executing findDeviceInfosByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 0de59ac8be..999e69f330 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -94,6 +94,15 @@ public interface DeviceRepository extends PagingAndSortingRepository findByTenantIdAndTypeAndFirmwareIdIsNull(@Param("tenantId") UUID tenantId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index 96313f7a45..a545c7cc6b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -149,6 +149,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao DaoUtil.toPageable(pageLink))); } + @Override + public PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(UUID tenantId, String type, PageLink pageLink) { + return DaoUtil.toPageData( + deviceRepository.findByTenantIdAndTypeAndFirmwareIdIsNull( + tenantId, + type, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } + @Override public PageData findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { return DaoUtil.toPageData( diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 18ef3e349c..ee3445c90d 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -41,7 +41,6 @@ zk: zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" cache: - # caffeine or redis type: "${CACHE_TYPE:redis}" redis: diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index b34f8940bc..e12c5308ba 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -36,7 +36,6 @@ zk: zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" cache: - # caffeine or redis type: "${CACHE_TYPE:redis}" redis: diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 25fee65cc0..6fa494f61b 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -41,7 +41,6 @@ zk: zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" cache: - # caffeine or redis type: "${CACHE_TYPE:redis}" redis: diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index ed30613043..6dbbb99311 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -41,7 +41,6 @@ zk: zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" cache: - # caffeine or redis type: "${CACHE_TYPE:redis}" redis: From 2658de715a9b1672bbf9abefd12b1479208a26cc Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Apr 2021 10:56:49 +0300 Subject: [PATCH 062/127] implemented consumerPropertiesPerTopic --- application/src/main/resources/thingsboard.yml | 4 ++++ .../queue/kafka/TbKafkaConsumerStatsService.java | 2 +- .../server/queue/kafka/TbKafkaConsumerTemplate.java | 2 +- .../server/queue/kafka/TbKafkaSettings.java | 11 ++++++++++- 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ae20c8c97c..29cd38d805 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -743,6 +743,10 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" + consumerPropertiesPerTopic: + tb_firmware: + - key: max.poll.records + value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java index 0ccf9ba42c..b29944c1e3 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerStatsService.java @@ -69,7 +69,7 @@ public class TbKafkaConsumerStatsService { this.adminClient = AdminClient.create(kafkaSettings.toAdminProps()); this.statsPrintScheduler = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("kafka-consumer-stats")); - Properties consumerProps = kafkaSettings.toConsumerProps(); + Properties consumerProps = kafkaSettings.toConsumerProps(null); consumerProps.put(ConsumerConfig.CLIENT_ID_CONFIG, "consumer-stats-loader-client"); consumerProps.put(ConsumerConfig.GROUP_ID_CONFIG, "consumer-stats-loader-client-group"); this.consumer = new KafkaConsumer<>(consumerProps); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java index 4429f3518f..fe4300a421 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java @@ -50,7 +50,7 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue String clientId, String groupId, String topic, TbQueueAdmin admin, TbKafkaConsumerStatsService statsService) { super(topic); - Properties props = settings.toConsumerProps(); + Properties props = settings.toConsumerProps(topic); props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId); if (groupId != null) { props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId); 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 86738acea8..f4a10f12c5 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 @@ -31,7 +31,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Properties; /** @@ -95,6 +97,9 @@ public class TbKafkaSettings { @Setter private List other; + @Setter + private Map> consumerPropertiesPerTopic; + public Properties toAdminProps() { Properties props = toProps(); props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, servers); @@ -103,7 +108,7 @@ public class TbKafkaSettings { return props; } - public Properties toConsumerProps() { + public Properties toConsumerProps(String topic) { Properties props = toProps(); props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers); props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords); @@ -113,6 +118,10 @@ public class TbKafkaSettings { props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class); props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, ByteArrayDeserializer.class); + + consumerPropertiesPerTopic + .getOrDefault(topic, Collections.emptyList()) + .forEach(kv -> props.put(kv.getKey(), kv.getValue())); return props; } From b82525ae864e49085cf0916aad691421505e3208 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Apr 2021 11:22:21 +0300 Subject: [PATCH 063/127] added consumerPropertiesPerTopic to transport yml --- .../server/queue/kafka/TbKafkaTopicConfigs.java | 15 +++++++++------ .../coap/src/main/resources/tb-coap-transport.yml | 4 ++++ .../http/src/main/resources/tb-http-transport.yml | 4 ++++ .../src/main/resources/tb-lwm2m-transport.yml | 4 ++++ .../mqtt/src/main/resources/tb-mqtt-transport.yml | 4 ++++ 5 files changed, 25 insertions(+), 6 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java index 16ec1be164..1a6a1a56e4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java @@ -19,6 +19,7 @@ import lombok.Getter; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import javax.annotation.PostConstruct; import java.util.HashMap; @@ -37,7 +38,7 @@ public class TbKafkaTopicConfigs { private String notificationsProperties; @Value("${queue.kafka.topic-properties.js-executor}") private String jsExecutorProperties; - @Value("${queue.kafka.topic-properties.fw-updates}") + @Value("${queue.kafka.topic-properties.fw-updates:}") private String fwUpdatesProperties; @Getter @@ -65,11 +66,13 @@ public class TbKafkaTopicConfigs { private Map getConfigs(String properties) { Map configs = new HashMap<>(); - for (String property : properties.split(";")) { - int delimiterPosition = property.indexOf(":"); - String key = property.substring(0, delimiterPosition); - String value = property.substring(delimiterPosition + 1); - configs.put(key, value); + if (StringUtils.isNotEmpty(properties)) { + for (String property : properties.split(";")) { + int delimiterPosition = property.indexOf(":"); + String key = property.substring(0, delimiterPosition); + String value = property.substring(delimiterPosition + 1); + configs.put(key, value); + } } return configs; } diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index ee3445c90d..3eb9e34885 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -136,6 +136,10 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" + consumerPropertiesPerTopic: + tb_firmware: + - key: max.poll.records + value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index e12c5308ba..363da4c0c9 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -108,6 +108,10 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" + consumerPropertiesPerTopic: + tb_firmware: + - key: max.poll.records + value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 6fa494f61b..0d2ab223d9 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -174,6 +174,10 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" + consumerPropertiesPerTopic: + tb_firmware: + - key: max.poll.records + value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 6dbbb99311..b95fccaf11 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -136,6 +136,10 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" + consumerPropertiesPerTopic: + tb_firmware: + - key: max.poll.records + value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" From 23063b16009a105a0eda1642779f7a372d6622fc Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Apr 2021 11:40:52 +0300 Subject: [PATCH 064/127] refactoring --- application/src/main/resources/thingsboard.yml | 2 +- .../org/thingsboard/server/queue/kafka/TbKafkaSettings.java | 2 +- transport/coap/src/main/resources/tb-coap-transport.yml | 4 ---- transport/http/src/main/resources/tb-http-transport.yml | 4 ---- transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml | 4 ---- transport/mqtt/src/main/resources/tb-mqtt-transport.yml | 4 ---- 6 files changed, 2 insertions(+), 18 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 29cd38d805..71232e2f5b 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -743,7 +743,7 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - consumerPropertiesPerTopic: + consumer-properties-per-topic: tb_firmware: - key: max.poll.records value: 10 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 f4a10f12c5..9933b87bbc 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 @@ -98,7 +98,7 @@ public class TbKafkaSettings { private List other; @Setter - private Map> consumerPropertiesPerTopic; + private Map> consumerPropertiesPerTopic = Collections.emptyMap(); public Properties toAdminProps() { Properties props = toProps(); diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 3eb9e34885..ee3445c90d 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -136,10 +136,6 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - consumerPropertiesPerTopic: - tb_firmware: - - key: max.poll.records - value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 363da4c0c9..e12c5308ba 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -108,10 +108,6 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - consumerPropertiesPerTopic: - tb_firmware: - - key: max.poll.records - value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 0d2ab223d9..6fa494f61b 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -174,10 +174,6 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - consumerPropertiesPerTopic: - tb_firmware: - - key: max.poll.records - value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index b95fccaf11..6dbbb99311 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -136,10 +136,6 @@ queue: sasl.mechanism: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM:PLAIN}" sasl.config: "${TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG:org.apache.kafka.common.security.plain.PlainLoginModule required username=\"CLUSTER_API_KEY\" password=\"CLUSTER_API_SECRET\";}" security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" - consumerPropertiesPerTopic: - tb_firmware: - - key: max.poll.records - value: 10 other: topic-properties: rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" From a477cad1b7acb45bddd45d4db107f0c414d3061c Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 22 Mar 2021 09:45:45 +0200 Subject: [PATCH 065/127] QueueController has return queue list from yaml config (TbQueueRuleEngineSettings). Tip: queue list consumes UI on device profile details --- .../server/controller/QueueController.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/QueueController.java b/application/src/main/java/org/thingsboard/server/controller/QueueController.java index 0786187066..cf0dcd8fae 100644 --- a/application/src/main/java/org/thingsboard/server/controller/QueueController.java +++ b/application/src/main/java/org/thingsboard/server/controller/QueueController.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.controller; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @@ -23,17 +24,23 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.queue.settings.TbQueueRuleEngineSettings; +import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; import org.thingsboard.server.queue.util.TbCoreComponent; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; @RestController @TbCoreComponent @RequestMapping("/api") public class QueueController extends BaseController { + @Autowired(required = false) + private TbQueueRuleEngineSettings ruleEngineSettings; + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/queues", params = {"serviceType"}, method = RequestMethod.GET) @ResponseBody @@ -43,7 +50,12 @@ public class QueueController extends BaseController { ServiceType type = ServiceType.valueOf(serviceType); switch (type) { case TB_RULE_ENGINE: - return Arrays.asList("Main", "HighPriority", "SequentialByOriginator"); + if (ruleEngineSettings == null) { + return Arrays.asList("Main", "HighPriority", "SequentialByOriginator"); + } + return ruleEngineSettings.getQueues().stream() + .map(TbRuleEngineQueueConfiguration::getName) + .collect(Collectors.toList()); default: return Collections.emptyList(); } From 1e3271ce71878373a321afd7aa8a1407da0833b7 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 23 Apr 2021 12:26:09 +0300 Subject: [PATCH 066/127] UI: move firmware-autocomplete to shared module; Add service map: DeviceProfileService, FirmwareService --- .../app/modules/home/components/home-components.module.ts | 7 ++----- ui-ngx/src/app/modules/home/models/services.map.ts | 6 +++++- .../firmware/firmware-autocomplete.component.html | 0 .../components/firmware/firmware-autocomplete.component.ts | 0 ui-ngx/src/app/shared/shared.module.ts | 3 +++ 5 files changed, 10 insertions(+), 6 deletions(-) rename ui-ngx/src/app/{modules/home => shared}/components/firmware/firmware-autocomplete.component.html (100%) rename ui-ngx/src/app/{modules/home => shared}/components/firmware/firmware-autocomplete.component.ts (100%) 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 e9d8fb7aa0..14ec1fdc4e 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 @@ -137,7 +137,6 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb import { EdgeDownlinkTableComponent } from '@home/components/edge/edge-downlink-table.component'; import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component'; import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; -import { FirmwareAutocompleteComponent } from '@home/components/firmware/firmware-autocomplete.component'; @NgModule({ declarations: @@ -254,8 +253,7 @@ import { FirmwareAutocompleteComponent } from '@home/components/firmware/firmwar ManageDashboardStatesDialogComponent, DashboardStateDialogComponent, EmbedDashboardDialogComponent, - DisplayWidgetTypesPanelComponent, - FirmwareAutocompleteComponent + DisplayWidgetTypesPanelComponent ], imports: [ CommonModule, @@ -361,8 +359,7 @@ import { FirmwareAutocompleteComponent } from '@home/components/firmware/firmwar ManageDashboardStatesDialogComponent, DashboardStateDialogComponent, EmbedDashboardDialogComponent, - DisplayWidgetTypesPanelComponent, - FirmwareAutocompleteComponent + DisplayWidgetTypesPanelComponent ], providers: [ WidgetComponentService, diff --git a/ui-ngx/src/app/modules/home/models/services.map.ts b/ui-ngx/src/app/modules/home/models/services.map.ts index 64e94f24df..c518249b77 100644 --- a/ui-ngx/src/app/modules/home/models/services.map.ts +++ b/ui-ngx/src/app/modules/home/models/services.map.ts @@ -34,6 +34,8 @@ import { AlarmService } from '@core/http/alarm.service'; import { Router } from '@angular/router'; import { BroadcastService } from '@core/services/broadcast.service'; import { ImportExportService } from '@home/components/import-export/import-export.service'; +import { DeviceProfileService } from '@core/http/device-profile.service'; +import { FirmwareService } from '@core/http/firmware.service'; export const ServicesMap = new Map>( [ @@ -55,6 +57,8 @@ export const ServicesMap = new Map>( ['translate', TranslateService], ['http', HttpClient], ['router', Router], - ['importExport', ImportExportService] + ['importExport', ImportExportService], + ['deviceProfileService', DeviceProfileService], + ['firmwareService', FirmwareService] ] ); diff --git a/ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.html b/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.html rename to ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html diff --git a/ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.ts b/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/components/firmware/firmware-autocomplete.component.ts rename to ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index 15d36d6753..925b297e77 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -141,6 +141,7 @@ import { FileSizePipe } from '@shared/pipe/file-size.pipe'; import { WidgetsBundleSearchComponent } from '@shared/components/widgets-bundle-search.component'; import { SelectableColumnsPipe } from '@shared/pipe/selectable-columns.pipe'; import { QuickTimeIntervalComponent } from '@shared/components/time/quick-time-interval.component'; +import { FirmwareAutocompleteComponent } from '@shared/components/firmware/firmware-autocomplete.component'; import { MAT_DATE_LOCALE } from '@angular/material/core'; @NgModule({ @@ -238,6 +239,7 @@ import { MAT_DATE_LOCALE } from '@angular/material/core'; HistorySelectorComponent, EntityGatewaySelectComponent, ContactComponent, + FirmwareAutocompleteComponent, WidgetsBundleSearchComponent ], imports: [ @@ -409,6 +411,7 @@ import { MAT_DATE_LOCALE } from '@angular/material/core'; HistorySelectorComponent, EntityGatewaySelectComponent, ContactComponent, + FirmwareAutocompleteComponent, WidgetsBundleSearchComponent ] }) From b0a27954ea43b6e745ca6e49220fea6a443cf69d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 23 Apr 2021 14:35:47 +0300 Subject: [PATCH 067/127] Improvements to FirmwareInfo --- .../main/data/upgrade/3.2.2/schema_update.sql | 1 + .../server/controller/FirmwareController.java | 6 +- .../firmware/DefaultFirmwareStateService.java | 25 +++++--- .../firmware/FirmwareUpdateStatus.java | 15 +++++ .../queue/DefaultTbCoreConsumerService.java | 16 ++--- .../server/common/data/Firmware.java | 12 ---- .../server/common/data/FirmwareInfo.java | 11 ++++ .../dao/firmware/BaseFirmwareService.java | 58 ++++++++++--------- .../server/dao/model/ModelConstants.java | 1 + .../server/dao/model/sql/FirmwareEntity.java | 6 ++ .../dao/model/sql/FirmwareInfoEntity.java | 41 ++++++++++++- .../sql/firmware/FirmwareInfoRepository.java | 6 +- .../resources/sql/schema-entities-hsql.sql | 1 + .../main/resources/sql/schema-entities.sql | 1 + .../server/dao/SqlDaoServiceTestSuite.java | 1 + .../dao/service/BaseFirmwareServiceTest.java | 7 +++ 16 files changed, 145 insertions(+), 63 deletions(-) diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index b138b5c254..e1a2952f64 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -70,6 +70,7 @@ CREATE TABLE IF NOT EXISTS firmware ( checksum_algorithm varchar(32), checksum varchar(1020), data bytea, + data_size bigint, additional_info varchar, search_text varchar(255), CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index 020a02b71c..c8f10b3d78 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -129,16 +129,18 @@ public class FirmwareController extends BaseController { firmware.setVersion(info.getVersion()); firmware.setAdditionalInfo(info.getAdditionalInfo()); + byte[] data = file.getBytes(); if (StringUtils.isEmpty(checksumAlgorithm)) { checksumAlgorithm = "sha256"; - checksum = Hashing.sha256().hashBytes(file.getBytes()).toString(); + checksum = Hashing.sha256().hashBytes(data).toString(); } firmware.setChecksumAlgorithm(checksumAlgorithm); firmware.setChecksum(checksum); firmware.setFileName(file.getOriginalFilename()); firmware.setContentType(file.getContentType()); - firmware.setData(ByteBuffer.wrap(file.getBytes())); + firmware.setData(ByteBuffer.wrap(data)); + firmware.setDataSize((long) data.length); return firmwareService.saveFirmware(firmware); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index d791b882ee..6e0e3fd246 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -23,6 +23,7 @@ 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.Firmware; +import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; @@ -174,12 +175,21 @@ public class DefaultFirmwareStateService implements FirmwareStateService { .setTs(ts) .build(); + FirmwareInfo firmware = firmwareService.findFirmwareInfoById(tenantId, firmwareId); + if (firmware == null) { + log.warn("[{}] Failed to send firmware update because firmware was already deleted", firmwareId); + return; + } + TopicPartitionInfo tpi = new TopicPartitionInfo(fwStateMsgProducer.getDefaultTopic(), null, null, false); fwStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); - BasicTsKvEntry status = new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name())); + List telemetry = new ArrayList<>(); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name()))); - telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { + telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { log.trace("[{}] Success save firmware status!", deviceId); @@ -193,16 +203,13 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } - private void update(Device device, Firmware firmware, long ts) { + private void update(Device device, FirmwareInfo firmware, long ts) { TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); - List telemetry = new ArrayList<>(); - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle()))); - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion()))); - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.INITIATED.name()))); + BasicTsKvEntry status = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.INITIATED.name())); - telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { + telemetryService.saveAndNotify(tenantId, deviceId, Collections.singletonList(status), new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { log.trace("[{}] Success save telemetry with target firmware for device!", deviceId); @@ -219,7 +226,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_TITLE, firmware.getTitle()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion()))); - attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(FIRMWARE_SIZE, (long) firmware.getData().array().length))); + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(FIRMWARE_SIZE, firmware.getDataSize()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum()))); telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java index d62265840a..ffaab79caf 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareUpdateStatus.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2021 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.firmware; public enum FirmwareUpdateStatus { 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 657320170f..de4eb903d9 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 @@ -151,8 +151,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> msgs = firmwareStatesConsumer.poll(getNotificationPollDuration()); if (msgs.isEmpty()) { - Thread.sleep(maxProcessingTimeoutPerRecord); continue; } long timeToSleep = maxProcessingTimeoutPerRecord; @@ -374,10 +373,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService 0) { - log.debug("Spent time per record is: [{}]!", spentTime); - Thread.sleep(timeToSleep); - timeToSleep = maxProcessingTimeoutPerRecord; + if (isSuccessUpdate) { + if (timeToSleep > 0) { + log.debug("Spent time per record is: [{}]!", spentTime); + Thread.sleep(timeToSleep); + timeToSleep = 0; + } + timeToSleep += maxProcessingTimeoutPerRecord; } } catch (Throwable e) { log.warn("Failed to process firmware update msg: {}", msg, e); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java b/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java index 6b8f48c5e8..b4155a578a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Firmware.java @@ -27,14 +27,6 @@ public class Firmware extends FirmwareInfo { private static final long serialVersionUID = 3091601761339422546L; - private String fileName; - - private String contentType; - - private String checksumAlgorithm; - - private String checksum; - private transient ByteBuffer data; public Firmware() { @@ -47,10 +39,6 @@ public class Firmware extends FirmwareInfo { public Firmware(Firmware firmware) { super(firmware); - this.fileName = firmware.getFileName(); - this.contentType = firmware.getContentType(); this.data = firmware.getData(); - this.checksumAlgorithm = firmware.getChecksumAlgorithm(); - this.checksum = firmware.getChecksum(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java index 224e94b59c..3a26763c4f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -32,6 +32,12 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo private String title; private String version; private boolean hasData; + private String fileName; + private String contentType; + private String checksumAlgorithm; + private String checksum; + private Long dataSize; + public FirmwareInfo() { super(); @@ -47,6 +53,11 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo this.title = firmwareInfo.getTitle(); this.version = firmwareInfo.getVersion(); this.hasData = firmwareInfo.isHasData(); + this.fileName = firmwareInfo.getFileName(); + this.contentType = firmwareInfo.getContentType(); + this.checksumAlgorithm = firmwareInfo.getChecksumAlgorithm(); + this.checksum = firmwareInfo.getChecksum(); + this.dataSize = firmwareInfo.getDataSize(); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index df3291b486..5a80fc75f6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -184,13 +184,7 @@ public class BaseFirmwareService implements FirmwareService { protected void validateUpdate(TenantId tenantId, FirmwareInfo firmware) { FirmwareInfo firmwareOld = firmwareInfoDao.findById(tenantId, firmware.getUuidId()); - if (!firmwareOld.getTitle().equals(firmware.getTitle())) { - throw new DataValidationException("Updating firmware title is prohibited!"); - } - - if (!firmwareOld.getVersion().equals(firmware.getVersion())) { - throw new DataValidationException("Updating firmware version is prohibited!"); - } + BaseFirmwareService.validateUpdate(firmware, firmwareOld); } }; @@ -261,35 +255,43 @@ public class BaseFirmwareService implements FirmwareService { protected void validateUpdate(TenantId tenantId, Firmware firmware) { Firmware firmwareOld = firmwareDao.findById(tenantId, firmware.getUuidId()); - if (!firmwareOld.getTitle().equals(firmware.getTitle())) { - throw new DataValidationException("Updating firmware title is prohibited!"); - } + BaseFirmwareService.validateUpdate(firmware, firmwareOld); - if (!firmwareOld.getVersion().equals(firmware.getVersion())) { - throw new DataValidationException("Updating firmware version is prohibited!"); + if (firmwareOld.getData() != null && !firmwareOld.getData().equals(firmware.getData())) { + throw new DataValidationException("Updating firmware data is prohibited!"); } + } + }; - if (firmwareOld.getFileName() != null && !firmwareOld.getFileName().equals(firmware.getFileName())) { - throw new DataValidationException("Updating firmware file name is prohibited!"); - } + private static void validateUpdate(FirmwareInfo firmware, FirmwareInfo firmwareOld) { + if (!firmwareOld.getTitle().equals(firmware.getTitle())) { + throw new DataValidationException("Updating firmware title is prohibited!"); + } - if (firmwareOld.getContentType() != null && !firmwareOld.getContentType().equals(firmware.getContentType())) { - throw new DataValidationException("Updating firmware content type is prohibited!"); - } + if (!firmwareOld.getVersion().equals(firmware.getVersion())) { + throw new DataValidationException("Updating firmware version is prohibited!"); + } - if (firmwareOld.getChecksumAlgorithm() != null && !firmwareOld.getChecksumAlgorithm().equals(firmware.getChecksumAlgorithm())) { - throw new DataValidationException("Updating firmware content type is prohibited!"); - } + if (firmwareOld.getFileName() != null && !firmwareOld.getFileName().equals(firmware.getFileName())) { + throw new DataValidationException("Updating firmware file name is prohibited!"); + } - if (firmwareOld.getChecksum() != null && !firmwareOld.getChecksum().equals(firmware.getChecksum())) { - throw new DataValidationException("Updating firmware content type is prohibited!"); - } + if (firmwareOld.getContentType() != null && !firmwareOld.getContentType().equals(firmware.getContentType())) { + throw new DataValidationException("Updating firmware content type is prohibited!"); + } - if (firmwareOld.getData() != null && !firmwareOld.getData().equals(firmware.getData())) { - throw new DataValidationException("Updating firmware data is prohibited!"); - } + if (firmwareOld.getChecksumAlgorithm() != null && !firmwareOld.getChecksumAlgorithm().equals(firmware.getChecksumAlgorithm())) { + throw new DataValidationException("Updating firmware content type is prohibited!"); } - }; + + if (firmwareOld.getChecksum() != null && !firmwareOld.getChecksum().equals(firmware.getChecksum())) { + throw new DataValidationException("Updating firmware content type is prohibited!"); + } + + if (firmwareOld.getDataSize() != null && !firmwareOld.getDataSize().equals(firmware.getDataSize())) { + throw new DataValidationException("Updating firmware data size is prohibited!"); + } + } private PaginatedRemover tenantFirmwareRemover = new PaginatedRemover<>() { 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 76a904b385..ae3b974054 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 @@ -483,6 +483,7 @@ public class ModelConstants { public static final String FIRMWARE_CHECKSUM_ALGORITHM_COLUMN = "checksum_algorithm"; public static final String FIRMWARE_CHECKSUM_COLUMN = "checksum"; public static final String FIRMWARE_DATA_COLUMN = "data"; + public static final String FIRMWARE_DATA_SIZE_COLUMN = "data_size"; public static final String FIRMWARE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY; public static final String FIRMWARE_HAS_DATA_PROPERTY = "has_data"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java index 2da0b110cc..ccfa0229bd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -38,6 +38,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_SIZE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; @@ -76,6 +77,9 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex @Column(name = FIRMWARE_DATA_COLUMN, columnDefinition = "BINARY") private byte[] data; + @Column(name = FIRMWARE_DATA_SIZE_COLUMN) + private Long dataSize; + @Type(type = "json") @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN) private JsonNode additionalInfo; @@ -98,6 +102,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex this.checksumAlgorithm = firmware.getChecksumAlgorithm(); this.checksum = firmware.getChecksum(); this.data = firmware.getData().array(); + this.dataSize = firmware.getDataSize(); this.additionalInfo = firmware.getAdditionalInfo(); } @@ -122,6 +127,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex firmware.setContentType(contentType); firmware.setChecksumAlgorithm(checksumAlgorithm); firmware.setChecksum(checksum); + firmware.setDataSize(dataSize); if (data != null) { firmware.setData(ByteBuffer.wrap(data)); firmware.setHasData(true); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java index ea0bd7ffe0..3549291e4b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java @@ -35,6 +35,12 @@ import javax.persistence.Table; import javax.persistence.Transient; import java.util.UUID; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ALGORITHM_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_SIZE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_HAS_DATA_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; @@ -58,6 +64,21 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Column(name = FIRMWARE_VERSION_COLUMN) private String version; + @Column(name = FIRMWARE_FILE_NAME_COLUMN) + private String fileName; + + @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) + private String contentType; + + @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN) + private String checksumAlgorithm; + + @Column(name = FIRMWARE_CHECKSUM_COLUMN) + private String checksum; + + @Column(name = FIRMWARE_DATA_SIZE_COLUMN) + private Long dataSize; + @Type(type = "json") @Column(name = ModelConstants.FIRMWARE_ADDITIONAL_INFO_COLUMN) private JsonNode additionalInfo; @@ -65,7 +86,6 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Column(name = SEARCH_TEXT_PROPERTY) private String searchText; -// @Column(name = FIRMWARE_HAS_DATA_PROPERTY, insertable = false, updatable = false) @Transient private boolean hasData; @@ -79,15 +99,27 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.tenantId = firmware.getTenantId().getId(); this.title = firmware.getTitle(); this.version = firmware.getVersion(); + this.fileName = firmware.getFileName(); + this.contentType = firmware.getContentType(); + this.checksumAlgorithm = firmware.getChecksumAlgorithm(); + this.checksum = firmware.getChecksum(); + this.dataSize = firmware.getDataSize(); this.additionalInfo = firmware.getAdditionalInfo(); } - public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version, Object additionalInfo, boolean hasData) { + public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version, + String fileName, String contentType, String checksumAlgorithm, String checksum, Long dataSize, + Object additionalInfo, boolean hasData) { this.id = id; this.createdTime = createdTime; this.tenantId = tenantId; this.title = title; this.version = version; + this.fileName = fileName; + this.contentType = contentType; + this.checksumAlgorithm = checksumAlgorithm; + this.checksum = checksum; + this.dataSize = dataSize; this.hasData = hasData; this.additionalInfo = JacksonUtil.convertValue(additionalInfo, JsonNode.class); } @@ -109,6 +141,11 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S firmware.setTenantId(new TenantId(tenantId)); firmware.setTitle(title); firmware.setVersion(version); + firmware.setFileName(fileName); + firmware.setContentType(contentType); + firmware.setChecksumAlgorithm(checksumAlgorithm); + firmware.setChecksum(checksum); + firmware.setDataSize(dataSize); firmware.setAdditionalInfo(additionalInfo); firmware.setHasData(hasData); return firmware; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java index 8dec49dca9..5601019c13 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java @@ -25,14 +25,14 @@ import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity; import java.util.UUID; public interface FirmwareInfoRepository extends CrudRepository { - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + "f.tenantId = :tenantId " + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") Page findAllByTenantId(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + "f.tenantId = :tenantId " + "AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") @@ -41,6 +41,6 @@ public interface FirmwareInfoRepository extends CrudRepository Date: Wed, 21 Apr 2021 09:40:31 +0300 Subject: [PATCH 068/127] remote js-executor: added slow query log, log config on service start --- .../api/jsInvokeMessageProcessor.js | 33 ++++++++++++++++--- .../config/custom-environment-variables.yml | 1 + msa/js-executor/config/default.yml | 3 +- msa/js-executor/server.js | 4 +++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index acba87ba57..d706e0adde 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -28,6 +28,9 @@ const config = require('config'), const scriptBodyTraceFrequency = Number(config.get('script.script_body_trace_frequency')); const useSandbox = config.get('script.use_sandbox') === 'true'; const maxActiveScripts = Number(config.get('script.max_active_scripts')); +const slowQueryLogMs = Number(config.get('script.slow_query_log_ms')); + +const {performance} = require('perf_hooks'); function JsInvokeMessageProcessor(producer) { console.log("Producer:", producer); @@ -39,13 +42,16 @@ function JsInvokeMessageProcessor(producer) { } JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { - + var tStart = performance.now(); let requestId; let responseTopic; + let headers; + let request; + let buf; try { - let request = JSON.parse(Buffer.from(message.data).toString('utf8')); - let headers = message.headers; - let buf = Buffer.from(headers.data['requestId']); + request = JSON.parse(Buffer.from(message.data).toString('utf8')); + headers = message.headers; + buf = Buffer.from(headers.data['requestId']); requestId = Utils.UUIDFromBuffer(buf); buf = Buffer.from(headers.data['responseTopic']); responseTopic = buf.toString('utf8'); @@ -66,6 +72,25 @@ JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { logger.error('[%s] Failed to process request: %s', requestId, err.message); logger.error(err.stack); } + + var tFinish = performance.now(); + var tTook = tFinish - tStart; + + if ( tTook > slowQueryLogMs ) { + let functionName; + if (request.invokeRequest) { + try { + buf = Buffer.from(request.invokeRequest['functionName']); + functionName = buf.toString('utf8'); + } catch (err){ + logger.error('[%s] Failed to read functionName from message header: %s', requestId, err.message); + logger.error(err.stack); + } + } + logger.warn('[%s] SLOW PROCESSING [%s]ms, functionName [%s], request: ', requestId, tTook, functionName); + logger.warn('request: %s', JSON.stringify(request, null, 4)) + } + } JsInvokeMessageProcessor.prototype.processCompileRequest = function(requestId, responseTopic, headers, compileRequest) { diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index 15c4e37fc0..848351217b 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -68,3 +68,4 @@ script: use_sandbox: "SCRIPT_USE_SANDBOX" script_body_trace_frequency: "SCRIPT_BODY_TRACE_FREQUENCY" max_active_scripts: "MAX_ACTIVE_SCRIPTS" + slow_query_log_ms: "SLOW_QUERY_LOG_MS" diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index fd60364247..8ada8f691b 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -55,5 +55,6 @@ logger: script: use_sandbox: "true" - script_body_trace_frequency: "1000" + script_body_trace_frequency: "1000000" max_active_scripts: "1000" + slow_query_log_ms: "0.5000" #millis diff --git a/msa/js-executor/server.js b/msa/js-executor/server.js index fcec2820a4..0c415cc156 100644 --- a/msa/js-executor/server.js +++ b/msa/js-executor/server.js @@ -15,6 +15,10 @@ */ const config = require('config'), logger = require('./config/logger')._logger('main'); +logger.info('===CONFIG BEGIN==='); +logger.info(JSON.stringify(config, null, 4)); +logger.info('===CONFIG END==='); + const serviceType = config.get('queue_type'); switch (serviceType) { case 'kafka': From c9f3562b5899176a127a5d8479603cf1db05f53e Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 21 Apr 2021 17:03:50 +0300 Subject: [PATCH 069/127] remote js-executor: parameter added KAFKA_CLIENT_ID --- msa/js-executor/config/custom-environment-variables.yml | 1 + msa/js-executor/config/default.yml | 1 + msa/js-executor/queue/kafkaTemplate.js | 7 +++++++ 3 files changed, 9 insertions(+) diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index 848351217b..bbe567d138 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -27,6 +27,7 @@ kafka: replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR" topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" use_confluent_cloud: "TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD" + client_id: "KAFKA_CLIENT_ID" #inject pod name to easy identify the client using /opt/kafka/bin/kafka-consumer-groups.sh confluent: sasl: mechanism: "TB_QUEUE_KAFKA_CONFLUENT_SASL_MECHANISM" diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 8ada8f691b..69c73abf9c 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -27,6 +27,7 @@ kafka: replication_factor: "1" topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1" use_confluent_cloud: false + client_id: "kafkajs" #inject pod name to easy identify the client using /opt/kafka/bin/kafka-consumer-groups.sh confluent: sasl: mechanism: "PLAIN" diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index b0b4de5e81..ae1e6ae3ca 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -21,6 +21,7 @@ const config = require('config'), KafkaJsWinstonLogCreator = require('../config/logger').KafkaJsWinstonLogCreator; const replicationFactor = config.get('kafka.replication_factor'); const topicProperties = config.get('kafka.topic_properties'); +const kafkaClientId = config.get('kafka.client_id'); let kafkaClient; let kafkaAdmin; @@ -62,6 +63,12 @@ function KafkaProducer() { logCreator: KafkaJsWinstonLogCreator }; + if (kafkaClientId) { + kafkaConfig['clientId'] = kafkaClientId; + } else { + logger.warn('KAFKA_CLIENT_ID is undefined. Consider to define the env variable KAFKA_CLIENT_ID'); + } + if (useConfluent) { kafkaConfig['sasl'] = { mechanism: config.get('kafka.confluent.sasl.mechanism'), From a569058c2964fd3a189b1a95a6d50ac8d97c71cc Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 21 Apr 2021 18:53:37 +0300 Subject: [PATCH 070/127] remote js-executor: added metric response sent to queue ms in debug level; parameter added SLOW_QUERY_LOG_BODY="true" to enable log bloat --- msa/js-executor/api/jsInvokeMessageProcessor.js | 10 ++++++++-- .../config/custom-environment-variables.yml | 3 ++- msa/js-executor/config/default.yml | 3 ++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index d706e0adde..a3acdc42da 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -29,6 +29,7 @@ const scriptBodyTraceFrequency = Number(config.get('script.script_body_trace_fre const useSandbox = config.get('script.use_sandbox') === 'true'; const maxActiveScripts = Number(config.get('script.max_active_scripts')); const slowQueryLogMs = Number(config.get('script.slow_query_log_ms')); +const slowQueryLogBody = config.get('script.slow_query_log_body') === 'true'; const {performance} = require('perf_hooks'); @@ -88,7 +89,9 @@ JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { } } logger.warn('[%s] SLOW PROCESSING [%s]ms, functionName [%s], request: ', requestId, tTook, functionName); - logger.warn('request: %s', JSON.stringify(request, null, 4)) + if (slowQueryLogBody) { + logger.info('Slow request body: %s', JSON.stringify(request, null, 4)) + } } } @@ -167,10 +170,13 @@ JsInvokeMessageProcessor.prototype.processReleaseRequest = function(requestId, r } JsInvokeMessageProcessor.prototype.sendResponse = function (requestId, responseTopic, headers, scriptId, compileResponse, invokeResponse, releaseResponse) { + var tStartSending = performance.now(); var remoteResponse = createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse); var rawResponse = Buffer.from(JSON.stringify(remoteResponse), 'utf8'); this.producer.send(responseTopic, scriptId, rawResponse, headers).then( - () => {}, + () => { + logger.debug('[%s] Response sent to queue, took [%s]ms, scriptId: [%s]', requestId, (performance.now() - tStartSending), scriptId); + }, (err) => { if (err) { logger.error('[%s] Failed to send response to queue: %s', requestId, err.message); diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index bbe567d138..0076095422 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -69,4 +69,5 @@ script: use_sandbox: "SCRIPT_USE_SANDBOX" script_body_trace_frequency: "SCRIPT_BODY_TRACE_FREQUENCY" max_active_scripts: "MAX_ACTIVE_SCRIPTS" - slow_query_log_ms: "SLOW_QUERY_LOG_MS" + slow_query_log_ms: "SLOW_QUERY_LOG_MS" #1.123456 + slow_query_log_body: "SLOW_QUERY_LOG_BODY" #true or false diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 69c73abf9c..8983b68368 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -58,4 +58,5 @@ script: use_sandbox: "true" script_body_trace_frequency: "1000000" max_active_scripts: "1000" - slow_query_log_ms: "0.5000" #millis + slow_query_log_ms: "1.000000" #millis + slow_query_log_body: "true" From c94dc5972e875d8d22f85c5ad0b236e6aa0e680d Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 21 Apr 2021 19:51:59 +0300 Subject: [PATCH 071/127] remote js-executor: parameter added TB_KAFKA_ACKS="1" --- msa/js-executor/api/jsInvokeMessageProcessor.js | 2 +- msa/js-executor/config/custom-environment-variables.yml | 1 + msa/js-executor/queue/kafkaTemplate.js | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index a3acdc42da..2964291a30 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -88,7 +88,7 @@ JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { logger.error(err.stack); } } - logger.warn('[%s] SLOW PROCESSING [%s]ms, functionName [%s], request: ', requestId, tTook, functionName); + logger.warn('[%s] SLOW PROCESSING [%s]ms, functionName [%s]', requestId, tTook, functionName); if (slowQueryLogBody) { logger.info('Slow request body: %s', JSON.stringify(request, null, 4)) } diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index 0076095422..e1a45e74d8 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -25,6 +25,7 @@ kafka: # Kafka Bootstrap Servers servers: "TB_KAFKA_SERVERS" replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR" + acks: "TB_KAFKA_ACKS" # -1 = all; 0 = no acknowledgments; 1 = only waits for the leader to acknowledge topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" use_confluent_cloud: "TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD" client_id: "KAFKA_CLIENT_ID" #inject pod name to easy identify the client using /opt/kafka/bin/kafka-consumer-groups.sh diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index ae1e6ae3ca..ad87c1efa4 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -const {logLevel, Kafka} = require('kafkajs'); +const {logLevel, Kafka, CompressionTypes} = require('kafkajs'); const config = require('config'), JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), @@ -22,6 +22,7 @@ const config = require('config'), const replicationFactor = config.get('kafka.replication_factor'); const topicProperties = config.get('kafka.topic_properties'); const kafkaClientId = config.get('kafka.client_id'); +const acks = config.get('kafka.acks'); let kafkaClient; let kafkaAdmin; @@ -35,6 +36,7 @@ function KafkaProducer() { return producer.send( { topic: responseTopic, + acks: acks, messages: [ { key: scriptId, From 3236d3ce9e4b33657bb380273c825d0477e88304 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 21 Apr 2021 19:52:46 +0300 Subject: [PATCH 072/127] remote js-executor: producer compression: CompressionTypes.GZIP --- msa/js-executor/queue/kafkaTemplate.js | 1 + 1 file changed, 1 insertion(+) diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index ad87c1efa4..deee3b95b9 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -37,6 +37,7 @@ function KafkaProducer() { { topic: responseTopic, acks: acks, + compression: CompressionTypes.GZIP, messages: [ { key: scriptId, From 01bf53b7f5a92214d6af98cad9f0cc6c09d09296 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 22 Apr 2021 12:11:17 +0300 Subject: [PATCH 073/127] remote js-executor: parameter added TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS --- msa/js-executor/config/custom-environment-variables.yml | 1 + msa/js-executor/config/default.yml | 2 ++ msa/js-executor/queue/kafkaTemplate.js | 8 ++++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index e1a45e74d8..8d2cf4b466 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -26,6 +26,7 @@ kafka: servers: "TB_KAFKA_SERVERS" replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR" acks: "TB_KAFKA_ACKS" # -1 = all; 0 = no acknowledgments; 1 = only waits for the leader to acknowledge + requestTimeout: "TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS" topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" use_confluent_cloud: "TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD" client_id: "KAFKA_CLIENT_ID" #inject pod name to easy identify the client using /opt/kafka/bin/kafka-consumer-groups.sh diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 8983b68368..84e927378a 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -25,6 +25,8 @@ kafka: # Kafka Bootstrap Servers servers: "localhost:9092" replication_factor: "1" + acks: "1" # -1 = all; 0 = no acknowledgments; 1 = only waits for the leader to acknowledge + requestTimeout: "30000" # The default value in kafkajs is: 30000 topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1" use_confluent_cloud: false client_id: "kafkajs" #inject pod name to easy identify the client using /opt/kafka/bin/kafka-consumer-groups.sh diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index deee3b95b9..fb56630471 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -19,10 +19,11 @@ const config = require('config'), JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), logger = require('../config/logger')._logger('kafkaTemplate'), KafkaJsWinstonLogCreator = require('../config/logger').KafkaJsWinstonLogCreator; -const replicationFactor = config.get('kafka.replication_factor'); +const replicationFactor = Number(config.get('kafka.replication_factor')); const topicProperties = config.get('kafka.topic_properties'); const kafkaClientId = config.get('kafka.client_id'); -const acks = config.get('kafka.acks'); +const acks = Number(config.get('kafka.acks')); +const requestTimeout = Number(config.get('kafka.requestTimeout')); let kafkaClient; let kafkaAdmin; @@ -72,6 +73,8 @@ function KafkaProducer() { logger.warn('KAFKA_CLIENT_ID is undefined. Consider to define the env variable KAFKA_CLIENT_ID'); } + kafkaConfig['requestTimeout'] = requestTimeout; + if (useConfluent) { kafkaConfig['sasl'] = { mechanism: config.get('kafka.confluent.sasl.mechanism'), @@ -117,6 +120,7 @@ function KafkaProducer() { logger.info('Started ThingsBoard JavaScript Executor Microservice.'); await consumer.run({ + //partitionsConsumedConcurrently: 1, // Default: 1 eachMessage: async ({topic, partition, message}) => { let headers = message.headers; let key = message.key; From c08e2ada1ff87ae88b1e348c30755528ba4297f4 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 22 Apr 2021 15:02:53 +0300 Subject: [PATCH 074/127] remote js-executor: parameter added KAFKA_CLIENT_ID --- msa/js-executor/config/custom-environment-variables.yml | 1 + msa/js-executor/config/default.yml | 1 + msa/js-executor/queue/kafkaTemplate.js | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index 8d2cf4b466..5e2bfe8342 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -27,6 +27,7 @@ kafka: replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR" acks: "TB_KAFKA_ACKS" # -1 = all; 0 = no acknowledgments; 1 = only waits for the leader to acknowledge requestTimeout: "TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS" + compression: "TB_QUEUE_KAFKA_COMPRESSION" # gzip or uncompressed topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" use_confluent_cloud: "TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD" client_id: "KAFKA_CLIENT_ID" #inject pod name to easy identify the client using /opt/kafka/bin/kafka-consumer-groups.sh diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 84e927378a..cb6b87d050 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -27,6 +27,7 @@ kafka: replication_factor: "1" acks: "1" # -1 = all; 0 = no acknowledgments; 1 = only waits for the leader to acknowledge requestTimeout: "30000" # The default value in kafkajs is: 30000 + compression: "gzip" # gzip or uncompressed topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1" use_confluent_cloud: false client_id: "kafkajs" #inject pod name to easy identify the client using /opt/kafka/bin/kafka-consumer-groups.sh diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index fb56630471..2f4fd8751d 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -24,6 +24,7 @@ const topicProperties = config.get('kafka.topic_properties'); const kafkaClientId = config.get('kafka.client_id'); const acks = Number(config.get('kafka.acks')); const requestTimeout = Number(config.get('kafka.requestTimeout')); +const compressionType = (config.get('kafka.requestTimeout') === "gzip") ? CompressionTypes.GZIP : CompressionTypes.None; let kafkaClient; let kafkaAdmin; @@ -38,7 +39,7 @@ function KafkaProducer() { { topic: responseTopic, acks: acks, - compression: CompressionTypes.GZIP, + compression: compressionType, messages: [ { key: scriptId, From 18f27b18fe022a85121724c88f8e6acd01139ff9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 22 Apr 2021 16:44:25 +0300 Subject: [PATCH 075/127] remote js-executor: adjusted default values --- msa/js-executor/config/default.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index cb6b87d050..cdf5b35421 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -59,7 +59,7 @@ logger: script: use_sandbox: "true" - script_body_trace_frequency: "1000000" + script_body_trace_frequency: "1000" max_active_scripts: "1000" slow_query_log_ms: "1.000000" #millis - slow_query_log_body: "true" + slow_query_log_body: "false" From 3fbbfea3319cb07659c0019fcd30caad94935e46 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Apr 2021 16:53:33 +0300 Subject: [PATCH 076/127] changed firmware data type to oid and updated firmwares caffeine cache config --- application/src/main/data/upgrade/3.2.2/schema_update.sql | 2 +- application/src/main/resources/thingsboard.yml | 4 ++-- .../org/thingsboard/server/dao/model/sql/FirmwareEntity.java | 2 ++ dao/src/main/resources/sql/schema-entities.sql | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index e1a2952f64..77a5a9dbbe 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -69,7 +69,7 @@ CREATE TABLE IF NOT EXISTS firmware ( content_type varchar(255), checksum_algorithm varchar(32), checksum varchar(1020), - data bytea, + data oid, data_size bigint, additional_info varchar, search_text varchar(255), diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index b41f188dd6..b8308c6611 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -372,8 +372,8 @@ caffeine: timeToLiveInMinutes: 20000 maxSize: 10000 firmwares: - timeToLiveInMinutes: 1440 - maxSize: 100 + timeToLiveInMinutes: 60 + maxSize: 10 edges: timeToLiveInMinutes: 1440 maxSize: 0 diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java index ccfa0229bd..690bb4e6d9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -30,6 +30,7 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.Lob; import javax.persistence.Table; import java.nio.ByteBuffer; import java.util.UUID; @@ -74,6 +75,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex @Column(name = FIRMWARE_CHECKSUM_COLUMN) private String checksum; + @Lob @Column(name = FIRMWARE_DATA_COLUMN, columnDefinition = "BINARY") private byte[] data; diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 7d7a18abc6..f71401ed57 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -186,7 +186,7 @@ CREATE TABLE IF NOT EXISTS firmware ( content_type varchar(255), checksum_algorithm varchar(32), checksum varchar(1020), - data bytea, + data oid, data_size bigint, additional_info varchar, search_text varchar(255), From 5f8a9e9f679f7f333a10bbabd51b22d519207144 Mon Sep 17 00:00:00 2001 From: nickAS21 <44275303+nickAS21@users.noreply.github.com> Date: Fri, 23 Apr 2021 16:56:24 +0300 Subject: [PATCH 077/127] Lwm2m rpc (#4473) * Lwm2m: RPC_terminal * Lwm2m: RPC_terminal del two file * Lwm2m: RPC_terminal add test observe * Lwm2m: RPC_terminal add test delete --- .../secure/LwM2MBootstrapSecurityStore.java | 6 +- ...LwM2mCredentialsSecurityInfoValidator.java | 7 +- .../lwm2m/server/LwM2mServerListener.java | 3 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 2 +- .../lwm2m/server/LwM2mTransportHandler.java | 216 ++++++++--- .../lwm2m/server/LwM2mTransportRequest.java | 334 +++++++++++------- .../lwm2m/server/LwM2mTransportService.java | 9 +- .../server/LwM2mTransportServiceImpl.java | 310 +++++++++------- .../lwm2m/server/client/LwM2mClient.java | 38 +- .../server/client/LwM2mClientContextImpl.java | 3 +- .../server/client/Lwm2mClientRpcRequest.java | 112 ++++++ .../server/client/ResultsResourceValue.java | 32 -- .../transport/lwm2m/utils/TypeServer.java | 29 -- 13 files changed, 723 insertions(+), 378 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/TypeServer.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 358ba499d4..306603cf78 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -35,7 +35,7 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInf import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; -import org.thingsboard.server.transport.lwm2m.utils.TypeServer; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; import java.io.IOException; import java.security.GeneralSecurityException; @@ -69,7 +69,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, TypeServer.BOOTSTRAP); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { /** add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); @@ -93,7 +93,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, TypeServer.BOOTSTRAP); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { /** add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index 86a333e361..bc8eb51b45 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -28,7 +28,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MC import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; -import org.thingsboard.server.transport.lwm2m.utils.TypeServer; import java.io.IOException; import java.security.GeneralSecurityException; @@ -59,7 +58,7 @@ public class LwM2mCredentialsSecurityInfoValidator { * @param keyValue - * @return ValidateDeviceCredentialsResponseMsg and SecurityInfo */ - public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, TypeServer keyValue) { + public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportHandler.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), @@ -96,7 +95,7 @@ public class LwM2mCredentialsSecurityInfoValidator { * @param keyValue - * @return SecurityInfo */ - private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, TypeServer keyValue) { + private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, LwM2mTransportHandler.LwM2mTypeServer keyValue) { ReadResultSecurityStore result = new ReadResultSecurityStore(); JsonObject objectMsg = LwM2mTransportHandler.validateJson(jsonStr); if (objectMsg != null && !objectMsg.isJsonNull()) { @@ -109,7 +108,7 @@ public class LwM2mCredentialsSecurityInfoValidator { && objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null; endPoint = (endPointPsk == null || endPointPsk.isEmpty()) ? endPoint : endPointPsk; if (object != null && !object.isJsonNull()) { - if (keyValue.equals(TypeServer.BOOTSTRAP)) { + if (keyValue.equals(LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP)) { result.setBootstrapJsonCredential(object); result.setEndPoint(endPoint); result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 9b2f3cb7eb..c17e37fd4a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -92,7 +92,8 @@ public class LwM2mServerListener { public void onResponse(Observation observation, Registration registration, ObserveResponse response) { if (registration != null) { try { - service.onObservationResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), response); + service.onObservationResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), + registration), response, null); } catch (Exception e) { log.error("[{}] onResponse", e.toString()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java index 0bc78254a6..6c0d6f29de 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java @@ -75,7 +75,7 @@ public class LwM2mSessionMsgListener implements GenericFutureListener 0) { @@ -254,9 +321,9 @@ public class LwM2mTransportHandler { objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(TELEMETRY) && !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonNull() && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).isJsonArray() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(OBSERVE) && - !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonNull() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE).isJsonArray() && + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(OBSERVE_LWM2M) && + !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE_LWM2M).isJsonNull() && + objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE_LWM2M).isJsonArray() && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(ATTRIBUTE_LWM2M) && !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonNull() && objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).isJsonObject()); @@ -341,8 +408,7 @@ public class LwM2mTransportHandler { if (keyArray.length > 1 && keyArray[1].split(LWM2M_SEPARATOR_KEY).length == 2) { keyArray[1] = keyArray[1].split(LWM2M_SEPARATOR_KEY)[0]; return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); - } - else { + } else { return pathIdVer; } } catch (Exception e) { @@ -350,6 +416,37 @@ public class LwM2mTransportHandler { } } + /** + * @param path - pathId or pathIdVer + * @return + */ + public static String getVerFromPathIdVerOrId(String path) { + try { + String[] keyArray = path.split(LWM2M_SEPARATOR_PATH); + if (keyArray.length > 1) { + String[] keyArrayVer = keyArray[1].split(LWM2M_SEPARATOR_KEY); + return keyArrayVer.length == 2 ? keyArrayVer[1] : null; + } + } catch (Exception e) { + return null; + } + return null; + } + + public static String validPathIdVer(String pathIdVer, Registration registration) throws IllegalArgumentException { + if (pathIdVer.indexOf(LWM2M_SEPARATOR_PATH) < 0) { + throw new IllegalArgumentException(String.format("Error:")); + } else { + String[] keyArray = pathIdVer.split(LWM2M_SEPARATOR_PATH); + if (keyArray.length > 1 && keyArray[1].split(LWM2M_SEPARATOR_KEY).length == 2) { + return pathIdVer; + } else { + LwM2mPath pathObjId = new LwM2mPath(pathIdVer); + return convertPathFromObjectIdToIdVer(pathIdVer, registration); + } + } + } + public static String convertPathFromObjectIdToIdVer(String path, Registration registration) { String ver = registration.getSupportedObject().get(new LwM2mPath(path).getObjectId()); try { @@ -357,8 +454,7 @@ public class LwM2mTransportHandler { if (keyArray.length > 1) { keyArray[1] = keyArray[1] + LWM2M_SEPARATOR_KEY + ver; return StringUtils.join(keyArray, LWM2M_SEPARATOR_PATH); - } - else { + } else { return path; } } catch (Exception e) { @@ -397,13 +493,13 @@ public class LwM2mTransportHandler { * when: * a.old value is 17 and new value is 24 due to lt condition * b.old value is 75 and new value is 90 due to both gt and step conditions - * String uriQueries = "pmin=10&pmax=60"; - * AttributeSet attributes = AttributeSet.parse(uriQueries); - * WriteAttributesRequest request = new WriteAttributesRequest(target, attributes); - * Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45")); - * Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10")); - * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); - * Attribute [] attrs = {gt, st}; + * String uriQueries = "pmin=10&pmax=60"; + * AttributeSet attributes = AttributeSet.parse(uriQueries); + * WriteAttributesRequest request = new WriteAttributesRequest(target, attributes); + * Attribute gt = new Attribute(GREATER_THAN, Double.valueOf("45")); + * Attribute st = new Attribute(LESSER_THAN, Double.valueOf("10")); + * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); + * Attribute [] attrs = {gt, st}; */ public static DownlinkRequest createWriteAttributeRequest(String target, Object params) { AttributeSet attrSet = new AttributeSet(createWriteAttributes(params)); @@ -423,4 +519,12 @@ public class LwM2mTransportHandler { }); return (Attribute[]) attributeLists.toArray(Attribute[]::new); } + + + public static Set convertJsonArrayToSet(JsonArray jsonArray) { + List attributeListOld = new Gson().fromJson(jsonArray, new TypeToken>() { + }.getType()); + return Sets.newConcurrentHashSet(attributeListOld); + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index d717c90418..9d7c22a112 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Response; @@ -24,8 +25,8 @@ import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.request.CancelObservationRequest; import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.request.DownlinkRequest; import org.eclipse.leshan.core.request.ExecuteRequest; @@ -47,27 +48,31 @@ import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; +import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; +import java.util.Arrays; import java.util.Date; +import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; +import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST; +import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_EXECUTE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_OBSERVE_CANCEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_UPDATE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_READ_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; @@ -79,7 +84,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle public class LwM2mTransportRequest { private ExecutorService executorResponse; - private LwM2mValueConverterImpl converter; + public LwM2mValueConverterImpl converter; private final LwM2mTransportContextServer lwM2mTransportContextServer; @@ -89,11 +94,16 @@ public class LwM2mTransportRequest { private final LwM2mTransportServiceImpl serviceImpl; - public LwM2mTransportRequest(LwM2mTransportContextServer lwM2mTransportContextServer, LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, LwM2mTransportServiceImpl serviceImpl) { + private final TransportService transportService; + + public LwM2mTransportRequest(LwM2mTransportContextServer lwM2mTransportContextServer, + LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, + LwM2mTransportServiceImpl serviceImpl, TransportService transportService) { this.lwM2mTransportContextServer = lwM2mTransportContextServer; this.lwM2mClientContext = lwM2mClientContext; this.leshanServer = leshanServer; this.serviceImpl = serviceImpl; + this.transportService = transportService; } @PostConstruct @@ -106,108 +116,155 @@ public class LwM2mTransportRequest { /** * Device management and service enablement, including Read, Write, Execute, Discover, Create, Delete and Write-Attributes * - * @param registration - - * @param targetIdVer - - * @param typeOper - - * @param contentFormatParam - - * @param observation - + * @param registration - + * @param targetIdVer - + * @param typeOper - + * @param contentFormatName - */ - public void sendAllRequest(Registration registration, String targetIdVer, String typeOper, - String contentFormatParam, Observation observation, Object params, long timeoutInMs) { - String target = convertPathFromIdVerToObjectId(targetIdVer); - LwM2mPath resultIds = new LwM2mPath(target); - if (registration != null && resultIds.getObjectId() >= 0) { + @SneakyThrows + public void sendAllRequest(Registration registration, String targetIdVer, LwM2mTypeOper typeOper, + String contentFormatName, Object params, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { + try { + + String target = convertPathFromIdVerToObjectId(targetIdVer); DownlinkRequest request = null; - ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null; - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); - ResourceModel resource = null; - timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; - switch (typeOper) { - case GET_TYPE_OPER_READ: - request = new ReadRequest(contentFormat, target); - break; - case GET_TYPE_OPER_DISCOVER: - request = new DiscoverRequest(target); - break; - case GET_TYPE_OPER_OBSERVE: - if (resultIds.isResource()) { - request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); - } else if (resultIds.isObjectInstance()) { - request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId()); - } else if (resultIds.getObjectId() >= 0) { - request = new ObserveRequest(resultIds.getObjectId()); - } - break; - case POST_TYPE_OPER_OBSERVE_CANCEL: - request = new CancelObservationRequest(observation); - break; - case POST_TYPE_OPER_EXECUTE: - resource = lwM2MClient.getResourceModel(targetIdVer); - if (params != null && resource != null && !resource.multiple) { - request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds)); - } else { - request = new ExecuteRequest(target); - } - break; - case POST_TYPE_OPER_WRITE_REPLACE: - // Request to write a String Single-Instance Resource using the TLV content format. - resource = lwM2MClient.getResourceModel(targetIdVer); - if (resource != null && contentFormat != null) { + ContentFormat contentFormat = contentFormatName != null ? ContentFormat.fromName(contentFormatName.toUpperCase()) : ContentFormat.DEFAULT; + LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2mClientWithReg(registration, null); + LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null; + if (!OBSERVE_READ_ALL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && + lwM2MClient != null) { + if (lwM2MClient.isValidObjectVersion(targetIdVer)) { + timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; + ResourceModel resourceModel = null; + switch (typeOper) { + case READ: + request = new ReadRequest(contentFormat, target); + break; + case DISCOVER: + request = new DiscoverRequest(target); + break; + case OBSERVE: + if (resultIds.isResource()) { + request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + } else if (resultIds.isObjectInstance()) { + request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId()); + } else if (resultIds.getObjectId() >= 0) { + request = new ObserveRequest(resultIds.getObjectId()); + } + break; + case OBSERVE_CANCEL: + /** + * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); + * At server side this will not remove the observation from the observation store, to do it you need to use + * {@code ObservationService#cancelObservation()} + */ + leshanServer.getObservationService().cancelObservations(registration, target); + break; + case EXECUTE: + resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + .getModelProvider()); + if (params != null && !resourceModel.multiple) { + request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModel.type, ResourceModel.Type.STRING, resultIds)); + } else { + request = new ExecuteRequest(target); + } + break; + case WRITE_REPLACE: + // Request to write a String Single-Instance Resource using the TLV content format. +// resource = lwM2MClient.getResourceModel(targetIdVer); // if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) { - if (contentFormat.equals(ContentFormat.TLV)) { - request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resource.type, registration); - } - // Mode.REPLACE && Request to write a String Single-Instance Resource using the given content format (TEXT, TLV, JSON) + resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + .getModelProvider()); + if (contentFormat.equals(ContentFormat.TLV)) { + request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), + resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModel.type, + registration, rpcRequest); + } + // Mode.REPLACE && Request to write a String Single-Instance Resource using the given content format (TEXT, TLV, JSON) // else if (!contentFormat.equals(ContentFormat.TLV) && !resource.multiple) { - else if (!contentFormat.equals(ContentFormat.TLV)) { - request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resource.type, registration); - } - } - break; - case PUT_TYPE_OPER_WRITE_UPDATE: - if (resultIds.getResourceId() >= 0) { -// ResourceModel resourceModel = leshanServer.getModelProvider().getObjectModel(registration).getObjectModel(resultIds.getObjectId()).resources.get(resultIds.getResourceId()); -// ResourceModel.Type typeRes = resourceModel.type; - LwM2mNode node = LwM2mSingleResource.newStringResource(resultIds.getResourceId(), (String) this.converter.convertValue(params, resource.type, ResourceModel.Type.STRING, resultIds)); - request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, target, node); + else if (!contentFormat.equals(ContentFormat.TLV)) { + request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), + resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModel.type, + registration, rpcRequest); + } + break; + case WRITE_UPDATE: +// LwM2mNode node = null; +// if (resultIds.isObjectInstance()) { +// node = new LwM2mObjectInstance(resultIds.getObjectInstanceId(), lwM2MClient. +// getNewResourcesForInstance(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider(), +// this.converter)); +// request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, target, node); +// } else if (resultIds.getObjectId() >= 0) { +// request = new ObserveRequest(resultIds.getObjectId()); +// } + break; + case WRITE_ATTRIBUTES: + request = createWriteAttributeRequest(target, params); + break; + case DELETE: + request = new DeleteRequest(target); + break; } - break; - case PUT_TYPE_OPER_WRITE_ATTRIBUTES: - request = createWriteAttributeRequest(target, params); - break; - } - if (request != null) { - try { - this.sendRequest(registration, lwM2MClient, request, timeoutInMs); - } catch (ClientSleepingException e) { - DownlinkRequest finalRequest = request; - long finalTimeoutInMs = timeoutInMs; - lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs)); - } catch (Exception e) { - log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper, e); + if (request != null) { + try { + this.sendRequest(registration, lwM2MClient, request, timeoutInMs, rpcRequest); + } catch (ClientSleepingException e) { + DownlinkRequest finalRequest = request; + long finalTimeoutInMs = timeoutInMs; + lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, rpcRequest)); + } catch (Exception e) { + log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper, e); + } + } else if (OBSERVE_CANCEL == typeOper && rpcRequest != null) { + rpcRequest.setInfoMsg(null); + serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), null, null); + } else { + log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper, targetIdVer); + if (rpcRequest != null) { + String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null"; + serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); + } + } + } else if (rpcRequest != null) { + String errorMsg = String.format("Path %s not found in object version", targetIdVer); + serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); + } + } else if (OBSERVE_READ_ALL.name().equals(typeOper.name())) { + Set observations = leshanServer.getObservationService().getObservations(registration); + Set observationPaths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); + String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO, + OBSERVE_READ_ALL.type, observationPaths); + serviceImpl.sendLogsToThingsboard(msg, registration); + log.info("[{}], [{}]", registration.getEndpoint(), msg); + if (rpcRequest != null) { + String valueMsg = String.format("Observation paths - %s", observationPaths); + serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE); } - } else { - log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper, targetIdVer); } + } catch (Exception e) { + String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR, + typeOper.name(), e.getMessage()); + serviceImpl.sendLogsToThingsboard(msg, registration); + throw new Exception(e); } } /** - * * @param registration - - * @param request - - * @param timeoutInMs - + * @param request - + * @param timeoutInMs - */ @SuppressWarnings("unchecked") - private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs) { + private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { leshanServer.send(registration, request, timeoutInMs, (ResponseCallback) response -> { if (!lwM2MClient.isInit()) { lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { - this.handleResponse(registration, request.getPath().toString(), response, request); + this.handleResponse(registration, request.getPath().toString(), response, request, rpcRequest); if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) { LwM2mNode node = ((WriteRequest) request).getNode(); Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(), @@ -216,48 +273,64 @@ public class LwM2mTransportRequest { LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(), value); serviceImpl.sendLogsToThingsboard(msg, registration); - log.debug("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(), + log.info("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), value); + serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO); } } else { String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString()); serviceImpl.sendLogsToThingsboard(msg, registration); log.error("[{}], [{}] - [{}] [{}] error SendRequest", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); + if (rpcRequest != null) { + serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR); + } } }, e -> { if (!lwM2MClient.isInit()) { lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } String msg = String.format("%s: sendRequest: Resource path - %s msg error - %s SendRequest to Client", - LOG_LW2M_ERROR, request.getPath().toString(), e.toString()); + LOG_LW2M_ERROR, request.getPath().toString(), e.getMessage()); serviceImpl.sendLogsToThingsboard(msg, registration); log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString()); + if (rpcRequest != null) { + serviceImpl.sentRpcRequest(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR); + } }); } - private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, Integer resourceId, Object value, ResourceModel.Type type, Registration registration) { + private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, + Integer resourceId, Object value, ResourceModel.Type type, + Registration registration, Lwm2mClientRpcRequest rpcRequest) { try { - switch (type) { - case STRING: // String - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, value.toString()) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, value.toString()); - case INTEGER: // Long - final long valueInt = Integer.toUnsignedLong(Integer.parseInt(value.toString())); - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, valueInt) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueInt); - case OBJLNK: // ObjectLink - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())); - case BOOLEAN: // Boolean - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Boolean.parseBoolean(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Boolean.parseBoolean(value.toString())); - case FLOAT: // Double - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.parseDouble(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.parseDouble(value.toString())); - case TIME: // Date - Date date = new Date(Long.decode(value.toString())); - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, date) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, date); - case OPAQUE: // byte[] value, base64 - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())); - default: + if (type != null) { + switch (type) { + case STRING: // String + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, value.toString()) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, value.toString()); + case INTEGER: // Long + final long valueInt = Integer.toUnsignedLong(Integer.parseInt(value.toString())); + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, valueInt) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueInt); + case OBJLNK: // ObjectLink + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())); + case BOOLEAN: // Boolean + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Boolean.parseBoolean(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Boolean.parseBoolean(value.toString())); + case FLOAT: // Double + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Double.parseDouble(value.toString())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.parseDouble(value.toString())); + case TIME: // Date + Date date = new Date(Long.decode(value.toString())); + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, date) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, date); + case OPAQUE: // byte[] value, base64 + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())); + default: + } + } + if (rpcRequest != null) { + String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; + String errorMsg = String.format("Bad ResourceModel Operations (E): Resource path - %s ResourceModel type - %s", patn, type); + rpcRequest.setErrorMsg(errorMsg); } return null; } catch (NumberFormatException e) { @@ -266,14 +339,19 @@ public class LwM2mTransportRequest { patn, type, value, e.toString()); serviceImpl.sendLogsToThingsboard(msg, registration); log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString()); + if (rpcRequest != null) { + String errorMsg = String.format("NumberFormatException: Resource path - %s type - %s value - %s", patn, type, value); + serviceImpl.sentRpcRequest(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); + } return null; } } - private void handleResponse(Registration registration, final String path, LwM2mResponse response, DownlinkRequest request) { + private void handleResponse(Registration registration, final String path, LwM2mResponse response, + DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) { executorResponse.submit(() -> { try { - sendResponse(registration, path, response, request); + this.sendResponse(registration, path, response, request, rpcRequest); } catch (Exception e) { log.error("[{}] endpoint [{}] path [{}] Exception Unable to after send response.", registration.getEndpoint(), path, e); } @@ -282,20 +360,30 @@ public class LwM2mTransportRequest { /** * processing a response from a client + * * @param registration - - * @param path - - * @param response - + * @param path - + * @param response - */ - private void sendResponse(Registration registration, String path, LwM2mResponse response, DownlinkRequest request) { + private void sendResponse(Registration registration, String path, LwM2mResponse response, + DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) { String pathIdVer = convertPathFromObjectIdToIdVer(path, registration); if (response instanceof ReadResponse) { - serviceImpl.onObservationResponse(registration, pathIdVer, (ReadResponse) response); + serviceImpl.onObservationResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest); } else if (response instanceof CancelObservationResponse) { log.info("[{}] Path [{}] CancelObservationResponse 3_Send", pathIdVer, response); + } else if (response instanceof DeleteResponse) { log.info("[{}] Path [{}] DeleteResponse 5_Send", pathIdVer, response); } else if (response instanceof DiscoverResponse) { - log.info("[{}] Path [{}] DiscoverResponse 6_Send", pathIdVer, response); + log.info("[{}] [{}] - [{}] [{}] Discovery value: [{}]", registration.getEndpoint(), + ((Response) response.getCoapResponse()).getCode(), response.getCode(), + request.getPath().toString(), ((DiscoverResponse) response).getObjectLinks()); + if (rpcRequest != null) { + String discoveryMsg = String.format("%s", + Arrays.stream(((DiscoverResponse) response).getObjectLinks()).collect(Collectors.toSet())); + serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), discoveryMsg, LOG_LW2M_VALUE); + } } else if (response instanceof ExecuteResponse) { log.info("[{}] Path [{}] ExecuteResponse 7_Send", pathIdVer, response); } else if (response instanceof WriteAttributesResponse) { @@ -304,5 +392,11 @@ public class LwM2mTransportRequest { log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", pathIdVer, response); serviceImpl.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request); } + if (rpcRequest != null && (response instanceof ExecuteResponse + || response instanceof WriteAttributesResponse + || response instanceof DeleteResponse)) { + rpcRequest.setInfoMsg(null); + serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null); + } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java index 4f3cfc44c8..530da1333c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.server.registration.Registration; 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.transport.lwm2m.server.client.Lwm2mClientRpcRequest; import java.util.Collection; import java.util.Optional; @@ -37,9 +38,7 @@ public interface LwM2mTransportService { void setCancelObservations(Registration registration); - void setCancelObservationRecourse(Registration registration, String path); - - void onObservationResponse(Registration registration, String path, ReadResponse response); + void onObservationResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest); void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo); @@ -51,7 +50,9 @@ public interface LwM2mTransportService { void onResourceDelete(Optional resourceDeleteMsgOpt); - void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest); + void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest, TransportProtos.SessionInfoProto sessionInfo); + + void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponse, TransportProtos.SessionInfoProto sessionInfo); void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java index 4a432ca131..5879ea8188 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java @@ -16,7 +16,6 @@ package org.thingsboard.server.transport.lwm2m.server; import com.fasterxml.jackson.core.type.TypeReference; -import com.google.common.collect.Sets; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; @@ -52,6 +51,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; +import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAnalyzerParameters; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -74,21 +74,29 @@ import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_DISCOVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_OBSERVE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.GET_TYPE_OPER_READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_EXECUTE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.POST_TYPE_OPER_WRITE_REPLACE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.PUT_TYPE_OPER_WRITE_ATTRIBUTES; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.DISCOVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.EXECUTE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_READ_ALL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.READ; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_ATTRIBUTES; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_REPLACE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertJsonArrayToSet; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey; @@ -257,20 +265,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { public void setCancelObservations(Registration registration) { if (registration != null) { Set observations = leshanServer.getObservationService().getObservations(registration); - observations.forEach(observation -> this.setCancelObservationRecourse(registration, observation.getPath().toString())); + observations.forEach(observation -> lwM2mTransportRequest.sendAllRequest(registration, + convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), OBSERVE_CANCEL, + null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); } } - /** - * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); - * At server side this will not remove the observation from the observation store, to do it you need to use - * {@code ObservationService#cancelObservation()} - */ - @Override - public void setCancelObservationRecourse(Registration registration, String path) { - leshanServer.getObservationService().cancelObservations(registration, path); - } - /** * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource * @@ -279,7 +279,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param response - observe */ @Override - public void onObservationResponse(Registration registration, String path, ReadResponse response) { + public void onObservationResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) { if (response.getContent() != null) { if (response.getContent() instanceof LwM2mObject) { LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); @@ -289,6 +289,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { this.updateObjectInstanceResourceValue(registration, lwM2mObjectInstance, path); } else if (response.getContent() instanceof LwM2mResource) { LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); + if (rpcRequest != null) { + Object valueResp = lwM2mResource.isMultiInstances() ? lwM2mResource.getValues() : lwM2mResource.getValue(); + Object value = this.converter.convertValue(valueResp, lwM2mResource.getType(), ResourceModel.Type.STRING, + new LwM2mPath(convertPathFromIdVerToObjectId(path))); + rpcRequest.setValueMsg(String.format("%s", value)); + this.sentRpcRequest(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); + } this.updateResourcesValue(registration, lwM2mResource, path); } } @@ -307,11 +314,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (msg.getSharedUpdatedCount() > 0) { msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); - String pathIdVer = this.validatePathIntoProfile(sessionInfo, pathName); + String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); if (pathIdVer != null) { - ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer); + ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + .getModelProvider()); if (resourceModel != null && resourceModel.operations.isWritable()) { this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer); } else { @@ -380,8 +388,124 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); } - public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest) { - log.info("[{}] toDeviceRpcRequest", toDeviceRequest); + @Override + public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest, SessionInfoProto sessionInfo) { + Lwm2mClientRpcRequest lwm2mClientRpcRequest = null; + try { + log.info("[{}] toDeviceRpcRequest", toDeviceRequest); + Registration registration = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).getRegistration(); + lwm2mClientRpcRequest = this.getDeviceRpcRequest(toDeviceRequest, sessionInfo, registration); + if (lwm2mClientRpcRequest != null && lwm2mClientRpcRequest.getErrorMsg() != null) { + lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); + this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); + } else { + lwM2mTransportRequest.sendAllRequest(registration, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), lwm2mClientRpcRequest.getContentFormatName(), + lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), + this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), lwm2mClientRpcRequest); + } + } catch (Exception e) { + if (lwm2mClientRpcRequest == null) { + lwm2mClientRpcRequest = new Lwm2mClientRpcRequest(); + } + lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); + if (lwm2mClientRpcRequest.getErrorMsg() == null) { + lwm2mClientRpcRequest.setErrorMsg(e.getMessage()); + } + this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); + } + } + + /** + * @param toDeviceRequest - + * @param sessionInfo - + * @param registration - + * @return + * @throws IllegalArgumentException + */ + private Lwm2mClientRpcRequest getDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest, + SessionInfoProto sessionInfo, Registration registration) throws IllegalArgumentException { + Lwm2mClientRpcRequest lwm2mClientRpcRequest = new Lwm2mClientRpcRequest(); + try { + lwm2mClientRpcRequest.setRequestId(toDeviceRequest.getRequestId()); + lwm2mClientRpcRequest.setSessionInfo(sessionInfo); + lwm2mClientRpcRequest.setValidTypeOper(toDeviceRequest.getMethodName()); + JsonObject rpcRequest = LwM2mTransportHandler.validateJson(toDeviceRequest.getParams()); + if (rpcRequest != null) { + if (rpcRequest.has(lwm2mClientRpcRequest.keyNameKey)) { + String targetIdVer = this.getPresentPathIntoProfile(sessionInfo, + rpcRequest.get(lwm2mClientRpcRequest.keyNameKey).getAsString()); + if (targetIdVer != null) { + lwm2mClientRpcRequest.setTargetIdVer(targetIdVer); + lwm2mClientRpcRequest.setInfoMsg(String.format("Changed by: key - %s, pathIdVer - %s", + rpcRequest.get(lwm2mClientRpcRequest.keyNameKey).getAsString(), targetIdVer)); + } + } + if (lwm2mClientRpcRequest.getTargetIdVer() == null) { + lwm2mClientRpcRequest.setValidTargetIdVerKey(rpcRequest, registration); + } + if (rpcRequest.has(lwm2mClientRpcRequest.contentFormatNameKey)) { + lwm2mClientRpcRequest.setValidContentFormatName(rpcRequest); + } + if (rpcRequest.has(lwm2mClientRpcRequest.timeoutInMsKey) && rpcRequest.get(lwm2mClientRpcRequest.timeoutInMsKey).getAsLong() > 0) { + lwm2mClientRpcRequest.setTimeoutInMs(rpcRequest.get(lwm2mClientRpcRequest.timeoutInMsKey).getAsLong()); + } + if (rpcRequest.has(lwm2mClientRpcRequest.valueKey)) { + lwm2mClientRpcRequest.setValue(rpcRequest.get(lwm2mClientRpcRequest.valueKey).getAsString()); + } + if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonObject()) { + lwm2mClientRpcRequest.setParams(new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey) + .getAsJsonObject().toString(), new TypeToken>() { + }.getType())); + } + lwm2mClientRpcRequest.setSessionInfo(sessionInfo); + if (OBSERVE_READ_ALL != lwm2mClientRpcRequest.getTypeOper() && lwm2mClientRpcRequest.getTargetIdVer() == null) { + lwm2mClientRpcRequest.setErrorMsg(lwm2mClientRpcRequest.targetIdVerKey + " and " + + lwm2mClientRpcRequest.keyNameKey + " is null or bad format"); + } + else if ((EXECUTE == lwm2mClientRpcRequest.getTypeOper() + || WRITE_REPLACE == lwm2mClientRpcRequest.getTypeOper()) + && lwm2mClientRpcRequest.getTargetIdVer() !=null + && !(new LwM2mPath(convertPathFromIdVerToObjectId(lwm2mClientRpcRequest.getTargetIdVer())).isResource() + || new LwM2mPath(convertPathFromIdVerToObjectId(lwm2mClientRpcRequest.getTargetIdVer())).isResourceInstance())) { + lwm2mClientRpcRequest.setErrorMsg("Invalid parameter " + lwm2mClientRpcRequest.targetIdVerKey + + ". Only Resource or ResourceInstance can be this operation"); + } + else if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()){ + lwm2mClientRpcRequest.setErrorMsg("Procedures In Development..."); + } + } else { + lwm2mClientRpcRequest.setErrorMsg("Params of request is bad Json format."); + } + } catch (Exception e) { + throw new IllegalArgumentException(lwm2mClientRpcRequest.getErrorMsg()); + } + return lwm2mClientRpcRequest; + } + + public void sentRpcRequest (Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { + rpcRequest.setResponseCode(requestCode); + if (LOG_LW2M_ERROR.equals(typeMsg)) { + rpcRequest.setInfoMsg(null); + rpcRequest.setValueMsg(null); + if (rpcRequest.getErrorMsg() == null) { + msg = msg.isEmpty() ? null : msg; + rpcRequest.setErrorMsg(msg); + } + } else if (LOG_LW2M_INFO.equals(typeMsg)) { + if (rpcRequest.getInfoMsg() == null) { + rpcRequest.setInfoMsg(msg); + } + } else if (LOG_LW2M_VALUE.equals(typeMsg)) { + if (rpcRequest.getValueMsg() == null) { + rpcRequest.setValueMsg(msg); + } + } + this.onToDeviceRpcResponse(rpcRequest.getDeviceRpcResponseResultMsg(), rpcRequest.getSessionInfo()); + } + + @Override + public void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceResponse, SessionInfoProto sessionInfo) { + transportService.process(sessionInfo, toDeviceResponse, null); } public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { @@ -395,8 +519,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { */ @Override public void doTrigger(Registration registration, String path) { - lwM2mTransportRequest.sendAllRequest(registration, path, POST_TYPE_OPER_EXECUTE, - ContentFormat.TLV.getName(), null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()); + lwM2mTransportRequest.sendAllRequest(registration, path, EXECUTE, + ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); } /** @@ -486,14 +610,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { // #2 lwM2MClient.getPendingRequests().addAll(clientObjects); - clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, GET_TYPE_OPER_READ, ContentFormat.TLV.getName(), - null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); + clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(), + null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); } // #1 - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_READ, clientObjects); - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_OBSERVE, clientObjects); - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, PUT_TYPE_OPER_WRITE_ATTRIBUTES, clientObjects); - this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, GET_TYPE_OPER_DISCOVER, clientObjects); + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, READ, clientObjects); + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, OBSERVE, clientObjects); + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, WRITE_ATTRIBUTES, clientObjects); + this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, DISCOVER, clientObjects); } } @@ -534,7 +658,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { */ private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); - if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())) { + if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + .getModelProvider())) { Set paths = new HashSet<>(); paths.add(path); this.updateAttrTelemetry(registration, paths); @@ -569,39 +694,6 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } } - /** - * @param clientProfile - - * @param path - - * @return true if path isPresent in postAttributeProfile - */ - private boolean validatePathInAttrProfile(LwM2mClientProfile clientProfile, String path) { - try { - List attributesSet = new Gson().fromJson(clientProfile.getPostAttributeProfile(), - new TypeToken>() { - }.getType()); - return attributesSet.stream().anyMatch(p -> p.equals(path)); - } catch (Exception e) { - log.error("Fail Validate Path [{}] ClientProfile.Attribute", path, e); - return false; - } - } - - /** - * @param clientProfile - - * @param path - - * @return true if path isPresent in postAttributeProfile - */ - private boolean validatePathInTelemetryProfile(LwM2mClientProfile clientProfile, String path) { - try { - List telemetriesSet = new Gson().fromJson(clientProfile.getPostTelemetryProfile(), new TypeToken>() { - }.getType()); - return telemetriesSet.stream().anyMatch(p -> p.equals(path)); - } catch (Exception e) { - log.error("Fail Validate Path [{}] ClientProfile.Telemetry", path, e); - return false; - } - } - /** * Start observe/read: Attr/Telemetry * #1 - Analyze: path in resource profile == client resource @@ -609,25 +701,24 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param registration - */ private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient, - String typeOper, Set clientObjects) { + LwM2mTypeOper typeOper, Set clientObjects) { LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); Set result = null; ConcurrentHashMap params = null; - if (GET_TYPE_OPER_READ.equals(typeOper)) { + if (READ.equals(typeOper)) { result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), new TypeReference<>() { }); result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(), new TypeReference<>() { })); - } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { + } else if (OBSERVE.equals(typeOper)) { result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), new TypeReference<>() { }); - } else if (GET_TYPE_OPER_DISCOVER.equals(typeOper)) { + } else if (DISCOVER.equals(typeOper)) { result = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()).keySet(); - ; - } else if (PUT_TYPE_OPER_WRITE_ATTRIBUTES.equals(typeOper)) { + } else if (WRITE_ATTRIBUTES.equals(typeOper)) { params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()); result = params.keySet(); } @@ -644,8 +735,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { lwM2MClient.getPendingRequests().addAll(pathSend); ConcurrentHashMap finalParams = params; pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), - null, finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); - if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { + finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); + if (OBSERVE.equals(typeOper)) { lwM2MClient.initValue(this, null); } } @@ -718,11 +809,6 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { return null; } -// public TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) { -// ResultsResourceValue resultsResourceValue = getResultsResourceValue(pathIdVer, registration); -// return resultsResourceValue != null ? this.lwM2mTransportContextServer.getKvAttrTelemetryToThingsboard(resultsResourceValue) : null; -// } - private TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) { LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2mClientWithReg(null, registration.getId()); JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); @@ -828,18 +914,18 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (lwM2mClientContext.addUpdateProfileParameters(deviceProfile)) { // #1 JsonArray attributeOld = lwM2MClientProfileOld.getPostAttributeProfile(); - Set attributeSetOld = this.convertJsonArrayToSet(attributeOld); + Set attributeSetOld = convertJsonArrayToSet(attributeOld); JsonArray telemetryOld = lwM2MClientProfileOld.getPostTelemetryProfile(); - Set telemetrySetOld = this.convertJsonArrayToSet(telemetryOld); + Set telemetrySetOld = convertJsonArrayToSet(telemetryOld); JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile(); JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile(); JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile(); LwM2mClientProfile lwM2MClientProfileNew = lwM2mClientContext.getProfiles().get(deviceProfile.getUuidId()); JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile(); - Set attributeSetNew = this.convertJsonArrayToSet(attributeNew); + Set attributeSetNew = convertJsonArrayToSet(attributeNew); JsonArray telemetryNew = lwM2MClientProfileNew.getPostTelemetryProfile(); - Set telemetrySetNew = this.convertJsonArrayToSet(telemetryNew); + Set telemetrySetNew = convertJsonArrayToSet(telemetryNew); JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile(); JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile(); JsonObject attributeLwm2mNew = lwM2MClientProfileNew.getPostAttributeLwm2mProfile(); @@ -882,7 +968,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { // update value in Resources registrationIds.forEach(registrationId -> { Registration registration = lwM2mClientContext.getRegistration(registrationId); - this.readResourceValueObserve(registration, sendAttrToThingsboard.getPathPostParametersAdd(), GET_TYPE_OPER_READ); + this.readResourceValueObserve(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ); // send attr/telemetry to tingsboard for new path this.updateAttrTelemetry(registration, sendAttrToThingsboard.getPathPostParametersAdd()); }); @@ -911,7 +997,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { registrationIds.forEach(registrationId -> { Registration registration = lwM2mClientContext.getRegistration(registrationId); if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) { - this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); + this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE); } // 5.3 del // send Request cancel observe to Client @@ -923,12 +1009,6 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } } - private Set convertJsonArrayToSet(JsonArray jsonArray) { - List attributeListOld = new Gson().fromJson(jsonArray, new TypeToken>() { - }.getType()); - return Sets.newConcurrentHashSet(attributeListOld); - } - /** * Compare old list with new list after change AttrTelemetryObserve in config Profile * @@ -962,16 +1042,16 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param registration - Registration LwM2M Client * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] */ - private void readResourceValueObserve(Registration registration, Set targets, String typeOper) { + private void readResourceValueObserve(Registration registration, Set targets, LwM2mTypeOper typeOper) { targets.forEach(target -> { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(target)); if (pathIds.isResource()) { - if (GET_TYPE_OPER_READ.equals(typeOper)) { + if (READ.equals(typeOper)) { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, - ContentFormat.TLV.getName(), null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()); - } else if (GET_TYPE_OPER_OBSERVE.equals(typeOper)) { + ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + } else if (OBSERVE.equals(typeOper)) { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, - null, null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()); + null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); } } }); @@ -1026,8 +1106,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { ConcurrentHashMap finalParams = lwm2mAttributesNew; - pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), - null, finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); + pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), + finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); } }); } @@ -1043,8 +1123,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { Map params = (Map) lwm2mAttributesOld.get(target); params.clear(); params.put(OBJECT_VERSION, ""); - lwM2mTransportRequest.sendAllRequest(registration, target, PUT_TYPE_OPER_WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), - null, params, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()); + lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), + params, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); }); } }); @@ -1054,9 +1134,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { private void cancelObserveIsValue(Registration registration, Set paramAnallyzer) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); - paramAnallyzer.forEach(p -> { - if (this.getResourceValueFromLwM2MClient(lwM2MClient, p) != null) { - this.setCancelObservationRecourse(registration, convertPathFromIdVerToObjectId(p)); + paramAnallyzer.forEach(pathIdVer -> { + if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) { + lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, OBSERVE_CANCEL, null, + null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); } } ); @@ -1064,8 +1145,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) { if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { - lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, POST_TYPE_OPER_WRITE_REPLACE, - ContentFormat.TLV.getName(), null, valueNew, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout()); + lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, WRITE_REPLACE, + ContentFormat.TLV.getName(), valueNew, + this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); } else { log.error("Failed update resource [{}] [{}]", path, valueNew); String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", @@ -1083,19 +1165,6 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); } - /** - * Get path to resource from profile equal keyName or from ModelObject equal name - * Only for resource: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase) - * - * @param sessionInfo - - * @param name - - * @return path if path isPresent in postProfile - */ - private String validatePathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { - String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, name); - return !pathIdVer.isEmpty() ? pathIdVer : null; - } - /** * Get path to resource from profile equal keyName * @@ -1108,7 +1177,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { LwM2mClient lwM2mClient = lwM2mClientContext.getLwM2MClient(sessionInfo); return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() .filter(e -> e.getValue().getAsString().equals(name) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().map(Map.Entry::getKey) - .orElse(""); + .orElse(null); } /** @@ -1140,7 +1209,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { public void updateAttriuteFromThingsboard(List tsKvProtos, TransportProtos.SessionInfoProto sessionInfo) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2MClient(sessionInfo); tsKvProtos.forEach(tsKvProto -> { - String pathIdVer = this.validatePathIntoProfile(sessionInfo, tsKvProto.getKv().getKey()); + String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, tsKvProto.getKv().getKey()); if (pathIdVer != null) { // #1.1 if (lwM2MClient.getDelayedRequests().containsKey(pathIdVer) && tsKvProto.getTs() > lwM2MClient.getDelayedRequests().get(pathIdVer).getTs()) { @@ -1276,7 +1345,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { - ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer); + ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + .getModelProvider()); Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId(); String objectVer = validateObjectVerFromKey(pathIdVer); return resourceModel != null && (isWritableNotOptional ? diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 5e736d8e0f..ac1738e101 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; @@ -27,7 +28,9 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServiceImpl; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Queue; @@ -39,7 +42,9 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.TRANSPORT_DEFAULT_LWM2M_VERSION; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getVerFromPathIdVerOrId; @Slf4j @Data @@ -94,12 +99,33 @@ public class LwM2mClient implements Cloneable { } } - public ResourceModel getResourceModel(String pathRez) { - if (this.getResources().get(pathRez) != null) { - return this.getResources().get(pathRez).getResourceModel(); - } else { - return null; - } + public ResourceModel getResourceModel(String pathRez, LwM2mModelProvider modelProvider) { + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); + String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + String verRez = getVerFromPathIdVerOrId(pathRez); + return (verRez == null || verSupportedObject.equals(verRez)) ? modelProvider.getObjectModel(registration) + .getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null; + } + + public Collection getNewResourcesForInstance(String pathRezIdVer, LwM2mModelProvider modelProvider, + LwM2mValueConverterImpl converter) { + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); + String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + String verRez = getVerFromPathIdVerOrId(pathRezIdVer); + Collection resources = ConcurrentHashMap.newKeySet(); + Map resourceModels = modelProvider.getObjectModel(registration) + .getObjectModel(pathIds.getObjectId()).resources; + resourceModels.forEach((k, resourceModel) -> { + resources.add(LwM2mSingleResource.newResource(k, converter.convertValue("0", ResourceModel.Type.STRING, resourceModel.type, pathIds), resourceModel.type)); + }); + return resources; + } + + public boolean isValidObjectVersion (String path) { + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(path)); + String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); + String verRez = getVerFromPathIdVerOrId(path); + return verRez == null ? TRANSPORT_DEFAULT_LWM2M_VERSION.equals(verSupportedObject) : verRez.equals(verSupportedObject); } /** diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index cca729f5ff..f496ed01b4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -26,7 +26,6 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; -import org.thingsboard.server.transport.lwm2m.utils.TypeServer; import java.util.Arrays; import java.util.Map; @@ -119,7 +118,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { */ @Override public LwM2mClient addLwM2mClientToSession(String identity) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, TypeServer.CLIENT); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandler.LwM2mTypeServer.CLIENT); if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; LwM2mClient client; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java new file mode 100644 index 0000000000..c71f0e61b0 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java @@ -0,0 +1,112 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server.client; + +import com.google.gson.JsonObject; +import lombok.Data; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.server.registration.Registration; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; + +import java.util.concurrent.ConcurrentHashMap; + +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validPathIdVer; + +@Data +public class Lwm2mClientRpcRequest { + public final String targetIdVerKey = "targetIdVer"; + public final String keyNameKey = "key"; + public final String typeOperKey = "typeOper"; + public final String contentFormatNameKey = "contentFormatName"; + public final String valueKey = "value"; + public final String infoKey = "info"; + public final String paramsKey = "params"; + public final String timeoutInMsKey = "timeOutInMs"; + public final String resultKey = "result"; + public final String errorKey = "error"; + public final String methodKey = "methodName"; + + private LwM2mTypeOper typeOper; + private String targetIdVer; + private String contentFormatName; + private long timeoutInMs; + private Object value; + private ConcurrentHashMap params; + private SessionInfoProto sessionInfo; + private int requestId; + private String errorMsg; + private String valueMsg; + private String infoMsg; + private String responseCode; + + public void setValidTypeOper (String typeOper){ + try { + this.typeOper = LwM2mTypeOper.fromLwLwM2mTypeOper(typeOper); + } catch (Exception e) { + this.errorMsg = this.methodKey + " - " + typeOper + " is not valid."; + } + } + public void setValidContentFormatName (JsonObject rpcRequest){ + try { + if (ContentFormat.fromName(rpcRequest.get(this.contentFormatNameKey).getAsString()) != null) { + this.contentFormatName = rpcRequest.get(this.contentFormatNameKey).getAsString(); + } + else { + this.errorMsg = this.contentFormatNameKey + " - " + rpcRequest.get(this.contentFormatNameKey).getAsString() + " is not valid."; + } + } catch (Exception e) { + this.errorMsg = this.contentFormatNameKey + " - " + rpcRequest.get(this.contentFormatNameKey).getAsString() + " is not valid."; + } + } + + public void setValidTargetIdVerKey (JsonObject rpcRequest, Registration registration){ + if (rpcRequest.has(this.targetIdVerKey)) { + String targetIdVerStr = rpcRequest.get(targetIdVerKey).getAsString(); + // targetIdVer without ver - ok + try { + // targetIdVer with/without ver - ok + this.targetIdVer = validPathIdVer(targetIdVerStr, registration); + if (this.targetIdVer != null){ + this.infoMsg = String.format("Changed by: pathIdVer - %s", this.targetIdVer); + } + } catch (Exception e) { + if (this.targetIdVer == null) { + this.errorMsg = this.targetIdVerKey + " - " + targetIdVerStr + " is not valid."; + } + } + } + } + + public TransportProtos.ToDeviceRpcResponseMsg getDeviceRpcResponseResultMsg() { + JsonObject payloadResp = new JsonObject(); + payloadResp.addProperty(this.resultKey, this.responseCode); + if (this.errorMsg != null) { + payloadResp.addProperty(this.errorKey, this.errorMsg); + } + else if (this.valueMsg != null) { + payloadResp.addProperty(this.valueKey, this.valueMsg); + } + else if (this.infoMsg != null) { + payloadResp.addProperty(this.infoKey, this.infoMsg); + } + return TransportProtos.ToDeviceRpcResponseMsg.newBuilder() + .setPayload(payloadResp.getAsJsonObject().toString()) + .setRequestId(this.requestId) + .build(); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java deleted file mode 100644 index 36cd8943fb..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsResourceValue.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright © 2016-2021 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.lwm2m.server.client; - -import lombok.Data; -import org.thingsboard.server.common.data.kv.DataType; - -@Data -public class ResultsResourceValue { - DataType dataType; - Object value; - String resourceName; - - public ResultsResourceValue (DataType dataType, Object value, String resourceName) { - this.dataType = dataType; - this.value = value; - this.resourceName = resourceName; - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/TypeServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/TypeServer.java deleted file mode 100644 index 732d761e68..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/TypeServer.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright © 2016-2021 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.lwm2m.utils; - -public enum TypeServer { - BOOTSTRAP(0, "bootstrap"), - CLIENT(1, "client"); - - public int code; - public String type; - - TypeServer(int code, String type) { - this.code = code; - this.type = type; - } -} From b2ba2025de28386a9dc5058f18adfd87cebae535 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Apr 2021 17:43:03 +0300 Subject: [PATCH 078/127] added firmware info cache --- .../firmware/DefaultFirmwareStateService.java | 4 +--- .../dao/firmware/BaseFirmwareService.java | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index 6e0e3fd246..7470079612 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -22,7 +22,6 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; 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.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.FirmwareId; @@ -35,7 +34,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; @@ -155,7 +153,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } if (targetFirmwareId.equals(currentFirmwareId)) { - update(device, firmwareService.findFirmwareById(device.getTenantId(), targetFirmwareId), ts); + update(device, firmwareService.findFirmwareInfoById(device.getTenantId(), targetFirmwareId), ts); isSuccess = true; } else { log.warn("[{}] [{}] Can`t update firmware for the device, target firmwareId: [{}], current firmwareId: [{}]!", tenantId, deviceId, targetFirmwareId, currentFirmwareId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 5a80fc75f6..7ddd48c2eb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; @@ -37,6 +38,7 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.nio.ByteBuffer; import java.util.Collections; +import java.util.List; import java.util.Optional; import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; @@ -68,7 +70,9 @@ public class BaseFirmwareService implements FirmwareService { try { FirmwareId firmwareId = firmwareInfo.getId(); if (firmwareId != null) { - cacheManager.getCache(FIRMWARE_CACHE).evict(firmwareId.toString()); + Cache cache = cacheManager.getCache(FIRMWARE_CACHE); + cache.evict(toFirmwareInfoKey(firmwareId)); + cache.evict(toFirmwareDataKey(firmwareId)); } return firmwareInfoDao.save(firmwareInfo.getTenantId(), firmwareInfo); } catch (Exception t) { @@ -88,7 +92,9 @@ public class BaseFirmwareService implements FirmwareService { try { FirmwareId firmwareId = firmware.getId(); if (firmwareId != null) { - cacheManager.getCache(FIRMWARE_CACHE).evict(firmwareId.toString()); + Cache cache = cacheManager.getCache(FIRMWARE_CACHE); + cache.evict(toFirmwareInfoKey(firmwareId)); + cache.evict(toFirmwareDataKey(firmwareId)); } return firmwareDao.save(firmware.getTenantId(), firmware); } catch (Exception t) { @@ -109,6 +115,7 @@ public class BaseFirmwareService implements FirmwareService { } @Override + @Cacheable(cacheNames = FIRMWARE_CACHE, key = "{#firmwareId}") public FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId) { log.trace("Executing findFirmwareInfoById [{}]", firmwareId); validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); @@ -137,7 +144,8 @@ public class BaseFirmwareService implements FirmwareService { validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); try { Cache cache = cacheManager.getCache(FIRMWARE_CACHE); - cache.evict(Collections.singletonList(firmwareId)); + cache.evict(toFirmwareInfoKey(firmwareId)); + cache.evict(toFirmwareDataKey(firmwareId)); firmwareDao.removeById(tenantId, firmwareId.getId()); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); @@ -316,4 +324,12 @@ public class BaseFirmwareService implements FirmwareService { return Optional.empty(); } } + + private static List toFirmwareInfoKey(FirmwareId firmwareId) { + return Collections.singletonList(firmwareId); + } + + private static String toFirmwareDataKey(FirmwareId firmwareId) { + return firmwareId.toString(); + } } From c8488aa52b48b20d649660544a19533134360898 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 23 Apr 2021 17:38:26 +0300 Subject: [PATCH 079/127] UI: Update firmware table config --- .../firmware/firmware-table-config.resolve.ts | 18 +++-- .../pages/firmware/firmwares.component.html | 75 ++++++++++++------- .../pages/firmware/firmwares.component.ts | 30 ++++++-- .../src/app/shared/models/firmware.models.ts | 9 ++- ui-ngx/src/app/shared/pipe/file-size.pipe.ts | 2 +- .../assets/locale/locale.constant-en_US.json | 8 +- 6 files changed, 99 insertions(+), 43 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts index 8fd2a8fccb..b809f6f403 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts @@ -29,7 +29,7 @@ import { FirmwareService } from '@core/http/firmware.service'; import { PageLink } from '@shared/models/page/page-link'; import { FirmwaresComponent } from '@home/pages/firmware/firmwares.component'; import { EntityAction } from '@home/models/entity/entity-component.models'; -import { DeviceInfo } from '@shared/models/device.models'; +import { FileSizePipe } from '@shared/pipe/file-size.pipe'; @Injectable() export class FirmwareTableConfigResolve implements Resolve> { @@ -38,7 +38,8 @@ export class FirmwareTableConfigResolve implements Resolve('createdTime', 'common.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'firmware.title', '50%'), - new EntityTableColumn('version', 'firmware.version', '50%') + new EntityTableColumn('title', 'firmware.title', '33%'), + new EntityTableColumn('version', 'firmware.version', '33%'), + new EntityTableColumn('fileName', 'firmware.file-name', '33%'), + new EntityTableColumn('dataSize', 'firmware.file-size', '70px', entity => { + return this.fileSize.transform(entity.dataSize || 0); + }), + new EntityTableColumn('checksum', 'firmware.checksum', '540px', entity => { + return `${entity.checksumAlgorithm}: ${entity.checksum}`; + }, () => ({}), false) ); this.config.cellActionDescriptors.push( { - name: this.translate.instant('firmware.export'), + name: this.translate.instant('firmware.download'), icon: 'file_download', isEnabled: (firmware) => firmware.hasData, onAction: ($event, entity) => this.exportFirmware($event, entity) diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html index c85ac61f0b..2911802a75 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html @@ -20,23 +20,31 @@ [disabled]="(isLoading$ | async) || !entity?.hasData" (click)="onEntityAction($event, 'uploadFirmware')" [fxShow]="!isEdit"> - {{'firmware.export' | translate }} + {{ 'firmware.download' | translate }}
+
@@ -46,39 +54,40 @@
firmware.title - + {{ 'firmware.title-required' | translate }} firmware.version - + {{ 'firmware.version-required' | translate }}
-
-
- - firmware.checksum-algorithm - - - - {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} - - - - - firmware.checksum - - - {{ 'firmware.checksum-required' | translate }} - - -
+
+ + firmware.checksum-algorithm + + + + + {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} + + + + + firmware.checksum + + + {{ 'firmware.checksum-required' | translate }} + + +
+
+
+
+ + firmware.file-name + + + + firmware.file-size-bytes + + + + firmware.content-type + + +
+
firmware.description diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts index 78d04ecf53..8ffef239b4 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts @@ -83,6 +83,8 @@ export class FirmwaresComponent extends EntityComponent implements OnI const form = this.fb.group({ title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]], + checksumAlgorithm: [entity ? entity.checksumAlgorithm : null], + checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)], additionalInfo: this.fb.group( { description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], @@ -90,28 +92,31 @@ export class FirmwaresComponent extends EntityComponent implements OnI ) }); if (this.isAdd) { - form.addControl('checksumAlgorithm', this.fb.control(null)); - form.addControl('checksum', this.fb.control('', Validators.maxLength(1020))); form.addControl('file', this.fb.control(null, Validators.required)); + } else { + form.addControl('fileName', this.fb.control(null)); + form.addControl('dataSize', this.fb.control(null)); + form.addControl('contentType', this.fb.control(null)); } return form; } updateForm(entity: Firmware) { - if (this.isEdit) { - this.entityForm.get('title').disable({emitEvent: false}); - this.entityForm.get('version').disable({emitEvent: false}); - } this.entityForm.patchValue({ title: entity.title, version: entity.version, + checksumAlgorithm: entity.checksumAlgorithm, + checksum: entity.checksum, + fileName: entity.fileName, + dataSize: entity.dataSize, + contentType: entity.contentType, additionalInfo: { description: entity.additionalInfo ? entity.additionalInfo.description : '' } }); } - onFirmwareIdCopied($event) { + onFirmwareIdCopied() { this.store.dispatch(new ActionNotificationShow( { message: this.translate.instant('firmware.idCopiedMessage'), @@ -121,4 +126,15 @@ export class FirmwaresComponent extends EntityComponent implements OnI horizontalPosition: 'right' })); } + + onFirmwareChecksumCopied() { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('firmware.checksum-copied-message'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } } diff --git a/ui-ngx/src/app/shared/models/firmware.models.ts b/ui-ngx/src/app/shared/models/firmware.models.ts index 81e4b6a66d..65dd1761bf 100644 --- a/ui-ngx/src/app/shared/models/firmware.models.ts +++ b/ui-ngx/src/app/shared/models/firmware.models.ts @@ -37,14 +37,15 @@ export interface FirmwareInfo extends BaseData { title?: string; version?: string; hasData?: boolean; + fileName: string; + checksum?: ChecksumAlgorithm; + checksumAlgorithm?: string; + contentType: string; + dataSize?: number; additionalInfo?: any; } export interface Firmware extends FirmwareInfo { file?: File; data: string; - fileName: string; - checksum?: ChecksumAlgorithm; - checksumAlgorithm?: string; - contentType: string; } diff --git a/ui-ngx/src/app/shared/pipe/file-size.pipe.ts b/ui-ngx/src/app/shared/pipe/file-size.pipe.ts index 6d43d42fce..a7f6b210e9 100644 --- a/ui-ngx/src/app/shared/pipe/file-size.pipe.ts +++ b/ui-ngx/src/app/shared/pipe/file-size.pipe.ts @@ -23,7 +23,7 @@ type unitPrecisionMap = { const defaultPrecisionMap: unitPrecisionMap = { bytes: 0, - KB: 0, + KB: 1, MB: 1, GB: 1, TB: 2, 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 a34693c7d5..71d097272c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1907,8 +1907,11 @@ "firmware": { "add": "Add firmware", "checksum": "Checksum", + "checksum-copied-message": "Firmware checksum has been copied to clipboard", "checksum-required": "Checksum is required.", "checksum-algorithm": "Checksum algorithm", + "content-type": "Content type", + "copy-checksum": "Copy checksum", "copyId": "Copy firmware Id", "description": "Description", "delete": "Delete firmware", @@ -1917,12 +1920,15 @@ "delete-firmwares-action-title": "Delete { count, plural, 1 {1 firmware} other {# firmwares} }", "delete-firmwares-text": "Be careful, after the confirmation all selected resources will be removed.", "delete-firmwares-title": "Are you sure you want to delete { count, plural, 1 {1 firmware} other {# firmwares} }?", + "download": "Download firmware", "drop-file": "Drop a firmware file or click to select a file to upload.", "empty": "Firmware is empty", - "export": "Export firmware", "idCopiedMessage": "Firmware Id has been copied to clipboard", "no-firmware-matching": "No firmware matching '{{entity}}' were found.", "no-firmware-text": "No firmwares found", + "file-name": "File name", + "file-size": "File size", + "file-size-bytes": "File size in bytes", "firmware": "Firmware", "firmware-details": "Firmware details", "firmware-required": "Firmware is required.", From 1d24a08c17df3788daebb4b2b67efa52ea6b07c6 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 23 Apr 2021 14:14:23 +0300 Subject: [PATCH 080/127] kafka: added TB_KAFKA_COMPRESSION_TYPE for producer: none (default), gzip. --- application/src/main/resources/thingsboard.yml | 1 + .../org/thingsboard/server/queue/kafka/TbKafkaSettings.java | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index b41f188dd6..270a18e065 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -730,6 +730,7 @@ queue: bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" acks: "${TB_KAFKA_ACKS:all}" retries: "${TB_KAFKA_RETRIES:1}" + compression.type: "${TB_KAFKA_COMPRESSION_TYPE:none}" # none or gzip batch.size: "${TB_KAFKA_BATCH_SIZE:16384}" linger.ms: "${TB_KAFKA_LINGER_MS:1}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" 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 9933b87bbc..708a6c6168 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 @@ -54,6 +54,9 @@ public class TbKafkaSettings { @Value("${queue.kafka.retries}") private int retries; + @Value("${queue.kafka.compression.type:none}") + private String compressionType; + @Value("${queue.kafka.batch.size}") private int batchSize; @@ -135,6 +138,7 @@ public class TbKafkaSettings { props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory); props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class); props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, ByteArraySerializer.class); + props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, compressionType); return props; } From 42438e4c95525242799a8c05e9eeb218013481ae Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 26 Apr 2021 13:15:37 +0300 Subject: [PATCH 081/127] Configure Docker setup for SNMP transport --- docker/.env | 1 + docker/docker-compose.aws-sqs.yml | 5 + docker/docker-compose.confluent.yml | 3 + docker/docker-compose.kafka.yml | 5 + docker/docker-compose.postgres.volumes.yml | 6 + docker/docker-compose.pubsub.yml | 5 + docker/docker-compose.rabbitmq.yml | 5 + docker/docker-compose.service-bus.yml | 5 + docker/docker-compose.yml | 12 + docker/docker-create-log-folders.sh | 2 + docker/tb-snmp-transport.env | 2 + .../server/msa/ThingsBoardDbInstaller.java | 11 +- .../src/main/resources/tb-snmp-transport.yml | 214 ++---------------- 13 files changed, 84 insertions(+), 192 deletions(-) create mode 100644 docker/tb-snmp-transport.env diff --git a/docker/.env b/docker/.env index f7b30aafe0..e63e1dbf8d 100644 --- a/docker/.env +++ b/docker/.env @@ -9,6 +9,7 @@ MQTT_TRANSPORT_DOCKER_NAME=tb-mqtt-transport HTTP_TRANSPORT_DOCKER_NAME=tb-http-transport COAP_TRANSPORT_DOCKER_NAME=tb-coap-transport LWM2M_TRANSPORT_DOCKER_NAME=tb-lwm2m-transport +SNMP_TRANSPORT_DOCKER_NAME=tb-snmp-transport TB_VERSION=latest diff --git a/docker/docker-compose.aws-sqs.yml b/docker/docker-compose.aws-sqs.yml index f70ad9410c..bbc5a3dcf6 100644 --- a/docker/docker-compose.aws-sqs.yml +++ b/docker/docker-compose.aws-sqs.yml @@ -74,3 +74,8 @@ services: - queue-aws-sqs.env depends_on: - zookeeper + tb-snmp-transport: + env_file: + - queue-aws-sqs.env + depends_on: + - zookeeper diff --git a/docker/docker-compose.confluent.yml b/docker/docker-compose.confluent.yml index e048ed0524..b0d50d47d9 100644 --- a/docker/docker-compose.confluent.yml +++ b/docker/docker-compose.confluent.yml @@ -58,3 +58,6 @@ services: tb-lwm2m-transport: env_file: - queue-confluent.env + tb-snmp-transport: + env_file: + - queue-confluent.env diff --git a/docker/docker-compose.kafka.yml b/docker/docker-compose.kafka.yml index e46df318c5..481c97def2 100644 --- a/docker/docker-compose.kafka.yml +++ b/docker/docker-compose.kafka.yml @@ -85,3 +85,8 @@ services: - queue-kafka.env depends_on: - kafka + tb-snmp-transport: + env_file: + - queue-kafka.env + depends_on: + - kafka diff --git a/docker/docker-compose.postgres.volumes.yml b/docker/docker-compose.postgres.volumes.yml index 2819bdf62d..510f5426d8 100644 --- a/docker/docker-compose.postgres.volumes.yml +++ b/docker/docker-compose.postgres.volumes.yml @@ -50,6 +50,9 @@ services: tb-mqtt-transport2: volumes: - tb-mqtt-transport-log-volume:/var/log/tb-mqtt-transport + tb-snmp-transport: + volumes: + - tb-snmp-transport-log-volume:/var/log/tb-snmp-transport volumes: postgres-db-volume: @@ -70,3 +73,6 @@ volumes: tb-mqtt-transport-log-volume: external: true name: ${TB_MQTT_TRANSPORT_LOG_VOLUME} + tb-snmp-transport-log-volume: + external: true + name: ${TB_SNMP_TRANSPORT_LOG_VOLUME} diff --git a/docker/docker-compose.pubsub.yml b/docker/docker-compose.pubsub.yml index 3041768951..cb09cdb266 100644 --- a/docker/docker-compose.pubsub.yml +++ b/docker/docker-compose.pubsub.yml @@ -74,3 +74,8 @@ services: - queue-pubsub.env depends_on: - zookeeper + tb-snmp-transport: + env_file: + - queue-pubsub.env + depends_on: + - zookeeper diff --git a/docker/docker-compose.rabbitmq.yml b/docker/docker-compose.rabbitmq.yml index b4d4b6cf3e..d070a8c1db 100644 --- a/docker/docker-compose.rabbitmq.yml +++ b/docker/docker-compose.rabbitmq.yml @@ -74,3 +74,8 @@ services: - queue-rabbitmq.env depends_on: - zookeeper + tb-snmp-transport: + env_file: + - queue-rabbitmq.env + depends_on: + - zookeeper diff --git a/docker/docker-compose.service-bus.yml b/docker/docker-compose.service-bus.yml index 29004d61fa..e2cadb3edb 100644 --- a/docker/docker-compose.service-bus.yml +++ b/docker/docker-compose.service-bus.yml @@ -72,3 +72,8 @@ services: - queue-service-bus.env depends_on: - zookeeper + tb-snmp-transport: + env_file: + - queue-service-bus.env + depends_on: + - zookeeper diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index d2440329ed..5da49759c2 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -217,6 +217,18 @@ services: - ./tb-transports/lwm2m/log:/var/log/tb-lwm2m-transport depends_on: - zookeeper + tb-snmp-transport: + restart: always + image: "${DOCKER_REPO}/${SNMP_TRANSPORT_DOCKER_NAME}:${TB_VERSION}" + environment: + TB_SERVICE_ID: tb-snmp-transport + env_file: + - tb-snmp-transport.env + volumes: + - ./tb-transports/snmp/conf:/config + - ./tb-transports/snmp/log:/var/log/tb-snmp-transport + depends_on: + - zookeeper tb-web-ui1: restart: always image: "${DOCKER_REPO}/${WEB_UI_DOCKER_NAME}:${TB_VERSION}" diff --git a/docker/docker-create-log-folders.sh b/docker/docker-create-log-folders.sh index 3ef3292f22..e0a23715a3 100755 --- a/docker/docker-create-log-folders.sh +++ b/docker/docker-create-log-folders.sh @@ -24,3 +24,5 @@ mkdir -p tb-transports/lwm2m/log && sudo chown -R 799:799 tb-transports/lwm2m/lo mkdir -p tb-transports/http/log && sudo chown -R 799:799 tb-transports/http/log mkdir -p tb-transports/mqtt/log && sudo chown -R 799:799 tb-transports/mqtt/log + +mkdir -p tb-transports/snmp/log && sudo chown -R 799:799 tb-transports/snmp/log diff --git a/docker/tb-snmp-transport.env b/docker/tb-snmp-transport.env new file mode 100644 index 0000000000..f92e30b78f --- /dev/null +++ b/docker/tb-snmp-transport.env @@ -0,0 +1,2 @@ +ZOOKEEPER_ENABLED=true +ZOOKEEPER_URL=zookeeper:2181 diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java index 3b1871b12b..6308b603c3 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -32,6 +32,7 @@ public class ThingsBoardDbInstaller extends ExternalResource { private final static String TB_LWM2M_TRANSPORT_LOG_VOLUME = "tb-lwm2m-transport-log-test-volume"; private final static String TB_HTTP_TRANSPORT_LOG_VOLUME = "tb-http-transport-log-test-volume"; private final static String TB_MQTT_TRANSPORT_LOG_VOLUME = "tb-mqtt-transport-log-test-volume"; + private final static String TB_SNMP_TRANSPORT_LOG_VOLUME = "tb-snmp-transport-log-test-volume"; private final DockerComposeExecutor dockerCompose; @@ -41,6 +42,7 @@ public class ThingsBoardDbInstaller extends ExternalResource { private final String tbLwm2mTransportLogVolume; private final String tbHttpTransportLogVolume; private final String tbMqttTransportLogVolume; + private final String tbSnmpTransportLogVolume; private final Map env; public ThingsBoardDbInstaller() { @@ -57,6 +59,7 @@ public class ThingsBoardDbInstaller extends ExternalResource { tbLwm2mTransportLogVolume = project + "_" + TB_LWM2M_TRANSPORT_LOG_VOLUME; tbHttpTransportLogVolume = project + "_" + TB_HTTP_TRANSPORT_LOG_VOLUME; tbMqttTransportLogVolume = project + "_" + TB_MQTT_TRANSPORT_LOG_VOLUME; + tbSnmpTransportLogVolume = project + "_" + TB_SNMP_TRANSPORT_LOG_VOLUME; dockerCompose = new DockerComposeExecutor(composeFiles, project); @@ -67,6 +70,7 @@ public class ThingsBoardDbInstaller extends ExternalResource { env.put("TB_LWM2M_TRANSPORT_LOG_VOLUME", tbLwm2mTransportLogVolume); env.put("TB_HTTP_TRANSPORT_LOG_VOLUME", tbHttpTransportLogVolume); env.put("TB_MQTT_TRANSPORT_LOG_VOLUME", tbMqttTransportLogVolume); + env.put("TB_SNMP_TRANSPORT_LOG_VOLUME", tbSnmpTransportLogVolume); dockerCompose.withEnv(env); } @@ -96,6 +100,9 @@ public class ThingsBoardDbInstaller extends ExternalResource { dockerCompose.withCommand("volume create " + tbMqttTransportLogVolume); dockerCompose.invokeDocker(); + dockerCompose.withCommand("volume create " + tbSnmpTransportLogVolume); + dockerCompose.invokeDocker(); + dockerCompose.withCommand("up -d redis postgres"); dockerCompose.invokeCompose(); @@ -117,9 +124,11 @@ public class ThingsBoardDbInstaller extends ExternalResource { copyLogs(tbLwm2mTransportLogVolume, "./target/tb-lwm2m-transport-logs/"); copyLogs(tbHttpTransportLogVolume, "./target/tb-http-transport-logs/"); copyLogs(tbMqttTransportLogVolume, "./target/tb-mqtt-transport-logs/"); + copyLogs(tbSnmpTransportLogVolume, "./target/tb-snmp-transport-logs/"); dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume + - " " + tbCoapTransportLogVolume + " " + tbLwm2mTransportLogVolume + " " + tbHttpTransportLogVolume + " " + tbMqttTransportLogVolume); + " " + tbCoapTransportLogVolume + " " + tbLwm2mTransportLogVolume + " " + tbHttpTransportLogVolume + + " " + tbMqttTransportLogVolume + " " + tbSnmpTransportLogVolume); dockerCompose.invokeDocker(); } diff --git a/transport/snmp/src/main/resources/tb-snmp-transport.yml b/transport/snmp/src/main/resources/tb-snmp-transport.yml index db9f79b51a..ae524d5047 100644 --- a/transport/snmp/src/main/resources/tb-snmp-transport.yml +++ b/transport/snmp/src/main/resources/tb-snmp-transport.yml @@ -14,13 +14,6 @@ # limitations under the License. # -server: - # Server bind address - address: "${HTTP_BIND_ADDRESS:0.0.0.0}" - # Server bind port - port: "${HTTP_BIND_PORT:8085}" - # Server SSL configuration - # Zookeeper connection parameters. Used for service discovery. zk: # Enable/disable zookeeper discovery service. @@ -36,146 +29,14 @@ zk: # Name of the directory in zookeeper 'filesystem' zk_dir: "${ZOOKEEPER_NODES_DIR:/thingsboard}" -cluster: - stats: - enabled: "${TB_CLUSTER_STATS_ENABLED:false}" - print_interval_ms: "${TB_CLUSTER_STATS_PRINT_INTERVAL_MS:10000}" - -cache: - # caffeine or redis - type: "${CACHE_TYPE:caffeine}" - attributes: - # make sure that if cache.type is 'redis' and cache.attributes.enabled is 'true' that you change 'maxmemory-policy' Redis config property to 'allkeys-lru', 'allkeys-lfu' or 'allkeys-random' - enabled: "${CACHE_ATTRIBUTES_ENABLED:true}" - -caffeine: - specs: - relations: - timeToLiveInMinutes: 1440 - maxSize: 0 - deviceCredentials: - timeToLiveInMinutes: 1440 - maxSize: 0 - devices: - timeToLiveInMinutes: 1440 - maxSize: 0 - sessions: - timeToLiveInMinutes: 1440 - maxSize: 0 - assets: - timeToLiveInMinutes: 1440 - maxSize: 0 - entityViews: - timeToLiveInMinutes: 1440 - maxSize: 0 - claimDevices: - timeToLiveInMinutes: 1 - maxSize: 0 - securitySettings: - timeToLiveInMinutes: 1440 - maxSize: 0 - tenantProfiles: - timeToLiveInMinutes: 1440 - maxSize: 0 - deviceProfiles: - timeToLiveInMinutes: 1440 - maxSize: 0 - attributes: - timeToLiveInMinutes: 1440 - maxSize: 100000 - -redis: - # standalone or cluster - connection: - type: "${REDIS_CONNECTION_TYPE:standalone}" - standalone: - host: "${REDIS_HOST:localhost}" - port: "${REDIS_PORT:6379}" - useDefaultClientConfig: "${REDIS_USE_DEFAULT_CLIENT_CONFIG:true}" - # this value may be used only if you used not default ClientConfig - clientName: "${REDIS_CLIENT_NAME:standalone}" - # this value may be used only if you used not default ClientConfig - connectTimeout: "${REDIS_CLIENT_CONNECT_TIMEOUT:30000}" - # this value may be used only if you used not default ClientConfig - readTimeout: "${REDIS_CLIENT_READ_TIMEOUT:60000}" - # this value may be used only if you used not default ClientConfig - usePoolConfig: "${REDIS_CLIENT_USE_POOL_CONFIG:false}" - cluster: - # Comma-separated list of "host:port" pairs to bootstrap from. - nodes: "${REDIS_NODES:}" - # Maximum number of redirects to follow when executing commands across the cluster. - max-redirects: "${REDIS_MAX_REDIRECTS:12}" - useDefaultPoolConfig: "${REDIS_USE_DEFAULT_POOL_CONFIG:true}" - # db index - db: "${REDIS_DB:0}" - # db password - password: "${REDIS_PASSWORD:}" - # pool config - pool_config: - maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" - maxIdle: "${REDIS_POOL_CONFIG_MAX_IDLE:128}" - minIdle: "${REDIS_POOL_CONFIG_MIN_IDLE:16}" - testOnBorrow: "${REDIS_POOL_CONFIG_TEST_ON_BORROW:true}" - testOnReturn: "${REDIS_POOL_CONFIG_TEST_ON_RETURN:true}" - testWhileIdle: "${REDIS_POOL_CONFIG_TEST_WHILE_IDLE:true}" - minEvictableMs: "${REDIS_POOL_CONFIG_MIN_EVICTABLE_MS:60000}" - evictionRunsMs: "${REDIS_POOL_CONFIG_EVICTION_RUNS_MS:30000}" - maxWaitMills: "${REDIS_POOL_CONFIG_MAX_WAIT_MS:60000}" - numberTestsPerEvictionRun: "${REDIS_POOL_CONFIG_NUMBER_TESTS_PER_EVICTION_RUN:3}" - blockWhenExhausted: "${REDIS_POOL_CONFIG_BLOCK_WHEN_EXHAUSTED:true}" - -# Check new version updates parameters -updates: - # Enable/disable updates checking. - enabled: "${UPDATES_ENABLED:true}" - -# spring freemarker configuration -spring.freemarker.checkTemplateLocation: "false" - -audit-log: - # Enable/disable audit log functionality. - enabled: "${AUDIT_LOG_ENABLED:true}" - # Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS - by_tenant_partitioning: "${AUDIT_LOG_BY_TENANT_PARTITIONING:MONTHS}" - # Number of days as history period if startTime and endTime are not specified - default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}" - # Logging levels per each entity type. - # Allowed values: OFF (disable), W (log write operations), RW (log read and write operations) - logging-level: - mask: - "device": "${AUDIT_LOG_MASK_DEVICE:W}" - "asset": "${AUDIT_LOG_MASK_ASSET:W}" - "dashboard": "${AUDIT_LOG_MASK_DASHBOARD:W}" - "customer": "${AUDIT_LOG_MASK_CUSTOMER:W}" - "user": "${AUDIT_LOG_MASK_USER:W}" - "rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}" - "alarm": "${AUDIT_LOG_MASK_ALARM:W}" - "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}" - "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}" - sink: - # Type of external sink. possible options: none, elasticsearch - type: "${AUDIT_LOG_SINK_TYPE:none}" - # Name of the index where audit logs stored - # Index name could contain next placeholders (not mandatory): - # @{TENANT} - substituted by tenant ID - # @{DATE} - substituted by current date in format provided in audit_log.sink.date_format - index_pattern: "${AUDIT_LOG_SINK_INDEX_PATTERN:@{TENANT}_AUDIT_LOG_@{DATE}}" - # Date format. Details of the pattern could be found here: - # https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html - date_format: "${AUDIT_LOG_SINK_DATE_FORMAT:YYYY.MM.DD}" - scheme_name: "${AUDIT_LOG_SINK_SCHEME_NAME:http}" # http or https - host: "${AUDIT_LOG_SINK_HOST:localhost}" - port: "${AUDIT_LOG_SINK_PORT:9200}" - user_name: "${AUDIT_LOG_SINK_USER_NAME:}" - password: "${AUDIT_LOG_SINK_PASSWORD:}" - -state: - # Should be greater then transport.sessions.report_timeout - defaultInactivityTimeoutInSec: "${DEFAULT_INACTIVITY_TIMEOUT:600}" - defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}" - persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}" - transport: + snmp: + enabled: "${SNMP_ENABLED:true}" + response_processing: + # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices + parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" + # to configure SNMP to work over UDP or TCP + underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" sessions: inactivity_timeout: "${TB_TRANSPORT_SESSIONS_INACTIVITY_TIMEOUT:300000}" report_timeout: "${TB_TRANSPORT_SESSIONS_REPORT_TIMEOUT:30000}" @@ -184,23 +45,9 @@ transport: type_cast_enabled: "${JSON_TYPE_CAST_ENABLED:true}" # Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check) max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}" - # Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property) - api_enabled: "${TB_TRANSPORT_API_ENABLED:true}" - # Local LwM2M transport parameters - snmp: - enabled: "${SNMP_ENABLED:true}" - response_processing: - # parallelism level for executor (workStealingPool) that is responsible for handling responses from SNMP devices - parallelism_level: "${SNMP_RESPONSE_PROCESSING_PARALLELISM_LEVEL:20}" - # to configure SNMP to work over UDP or TCP - underlying_protocol: "${SNMP_UNDERLYING_PROTOCOL:udp}" queue: - type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) - in_memory: - stats: - # For debug lvl - print-interval-ms: "${TB_QUEUE_IN_MEMORY_STATS_PRINT_INTERVAL_MS:60000}" + type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) kafka: bootstrap.servers: "${TB_KAFKA_SERVERS:localhost:9092}" acks: "${TB_KAFKA_ACKS:all}" @@ -209,10 +56,6 @@ queue: linger.ms: "${TB_KAFKA_LINGER_MS:1}" buffer.memory: "${TB_BUFFER_MEMORY:33554432}" replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}" - max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}" - max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}" - max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}" - fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}" use_confluent_cloud: "${TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD:false}" confluent: ssl.algorithm: "${TB_QUEUE_KAFKA_CONFLUENT_SSL_ALGORITHM:https}" @@ -221,11 +64,11 @@ queue: security.protocol: "${TB_QUEUE_KAFKA_CONFLUENT_SECURITY_PROTOCOL:SASL_SSL}" other: topic-properties: - rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1}" - js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100}" + rule-engine: "${TB_QUEUE_KAFKA_RE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + core: "${TB_QUEUE_KAFKA_CORE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + transport-api: "${TB_QUEUE_KAFKA_TA_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + notifications: "${TB_QUEUE_KAFKA_NOTIFICATIONS_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:1048576000;partitions:1;min.insync.replicas:1}" + js-executor: "${TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES:retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100;min.insync.replicas:1}" consumer-stats: enabled: "${TB_QUEUE_KAFKA_CONSUMER_STATS_ENABLED:true}" print-interval-ms: "${TB_QUEUE_KAFKA_CONSUMER_STATS_MIN_PRINT_INTERVAL_MS:60000}" @@ -282,7 +125,8 @@ queue: notifications: "${TB_QUEUE_RABBIT_MQ_NOTIFICATIONS_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" js-executor: "${TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES:x-max-length-bytes:1048576000;x-message-ttl:604800000}" partitions: - hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" # murmur3_32, murmur3_128 or sha256 + hash_function_name: "${TB_QUEUE_PARTITIONS_HASH_FUNCTION_NAME:murmur3_128}" + virtual_nodes_size: "${TB_QUEUE_PARTITIONS_VIRTUAL_NODES_SIZE:16}" transport_api: requests_topic: "${TB_QUEUE_TRANSPORT_API_REQUEST_TOPIC:tb_transport.api.requests}" responses_topic: "${TB_QUEUE_TRANSPORT_API_RESPONSE_TOPIC:tb_transport.api.responses}" @@ -295,11 +139,11 @@ queue: topic: "${TB_QUEUE_CORE_TOPIC:tb_core}" poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" - pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}" + pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: - enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" - print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}" + enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" + print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" js: # JS Eval request topic request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}" @@ -308,15 +152,13 @@ queue: # JS Eval max pending requests max_pending_requests: "${REMOTE_JS_MAX_PENDING_REQUESTS:10000}" # JS Eval max request timeout - max_eval_requests_timeout: "${REMOTE_JS_MAX_EVAL_REQUEST_TIMEOUT:60000}" - # JS max request timeout max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}" # JS response poll interval response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}" rule-engine: topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}" poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}" - pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:2000}" + pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}" stats: enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}" print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}" @@ -325,7 +167,7 @@ queue: topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}" poll-interval: "${TB_QUEUE_RE_MAIN_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_RE_MAIN_PARTITIONS:10}" - pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:2000}" + pack-processing-timeout: "${TB_QUEUE_RE_MAIN_PACK_PROCESSING_TIMEOUT_MS:60000}" submit-strategy: type: "${TB_QUEUE_RE_MAIN_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL # For BATCH only @@ -336,12 +178,11 @@ queue: retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited failure-percentage: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_RETRY_PAUSE:3}"# Time in seconds to wait in consumer thread before retries; - max-pause-between-retries: "${TB_QUEUE_RE_MAIN_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:3}"# Max allowed time in seconds for pause between retries. - name: "${TB_QUEUE_RE_HP_QUEUE_NAME:HighPriority}" topic: "${TB_QUEUE_RE_HP_TOPIC:tb_rule_engine.hp}" poll-interval: "${TB_QUEUE_RE_HP_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_RE_HP_PARTITIONS:10}" - pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:2000}" + pack-processing-timeout: "${TB_QUEUE_RE_HP_PACK_PROCESSING_TIMEOUT_MS:60000}" submit-strategy: type: "${TB_QUEUE_RE_HP_SUBMIT_STRATEGY_TYPE:BURST}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL # For BATCH only @@ -352,12 +193,11 @@ queue: retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRIES:0}" # Number of retries, 0 is unlimited failure-percentage: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; - max-pause-between-retries: "${TB_QUEUE_RE_HP_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries. - name: "${TB_QUEUE_RE_SQ_QUEUE_NAME:SequentialByOriginator}" topic: "${TB_QUEUE_RE_SQ_TOPIC:tb_rule_engine.sq}" poll-interval: "${TB_QUEUE_RE_SQ_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_RE_SQ_PARTITIONS:10}" - pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:2000}" + pack-processing-timeout: "${TB_QUEUE_RE_SQ_PACK_PROCESSING_TIMEOUT_MS:60000}" submit-strategy: type: "${TB_QUEUE_RE_SQ_SUBMIT_STRATEGY_TYPE:SEQUENTIAL_BY_ORIGINATOR}" # BURST, BATCH, SEQUENTIAL_BY_ORIGINATOR, SEQUENTIAL_BY_TENANT, SEQUENTIAL # For BATCH only @@ -368,16 +208,11 @@ queue: retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRIES:3}" # Number of retries, 0 is unlimited failure-percentage: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_FAILURE_PERCENTAGE:0}" # Skip retry if failures or timeouts are less then X percentage of messages; pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_RETRY_PAUSE:5}"# Time in seconds to wait in consumer thread before retries; - max-pause-between-retries: "${TB_QUEUE_RE_SQ_PROCESSING_STRATEGY_MAX_RETRY_PAUSE:5}"# Max allowed time in seconds for pause between retries. transport: # For high priority notifications that require minimum latency and processing time notifications_topic: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_TOPIC:tb_transport.notifications}" poll_interval: "${TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS:25}" -event: - debug: - max-symbols: "${TB_MAX_DEBUG_EVENT_SYMBOLS:4096}" - service: type: "${TB_SERVICE_TYPE:tb-transport}" # Unique id for this service (autogenerated if empty) @@ -387,13 +222,10 @@ service: metrics: # Enable/disable actuator metrics. enabled: "${METRICS_ENABLED:false}" - timer: - # Metrics percentiles returned by actuator for timer metrics. List of double values (divided by ,). - percentiles: "${METRICS_TIMER_PERCENTILES:0.5}" management: endpoints: web: exposure: # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics). - include: '${METRICS_ENDPOINTS_EXPOSE:info}' + include: '${METRICS_ENDPOINTS_EXPOSE:info}' \ No newline at end of file From 7ce1fc77c92bb405866c34c6481d4f23ad3858c2 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Mon, 26 Apr 2021 13:41:31 +0300 Subject: [PATCH 082/127] Use session report timeout to remove staled sessions --- .../server/actors/device/DeviceActorMessageProcessor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index c90c47fb1c..db81941b00 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -720,7 +720,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } void initSessionTimeout(TbActorCtx ctx) { - schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionInactivityTimeout(), systemContext.getSessionInactivityTimeout()); + schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout()); } void checkSessionsTimeout() { From 1850024fdb7902583d509f1788f8f3a834d10572 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Apr 2021 10:26:16 +0300 Subject: [PATCH 083/127] implemented firmware update for the coap transport --- .../common/msg/session/FeatureType.java | 2 +- .../common/msg/session/SessionMsgType.java | 4 +- .../transport/coap/CoapTransportResource.java | 48 ++++++++++++++++++- .../transport/coap/CoapTransportService.java | 1 - .../server/dao/tenant/TenantServiceImpl.java | 5 ++ 5 files changed, 55 insertions(+), 5 deletions(-) diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java index 9be2957401..2756276dc4 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java @@ -16,5 +16,5 @@ package org.thingsboard.server.common.msg.session; public enum FeatureType { - ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION + ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION, FIRMWARE } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java index 5dbea04d59..a6f05018f8 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java @@ -30,7 +30,9 @@ public enum SessionMsgType { SESSION_OPEN, SESSION_CLOSE, - CLAIM_REQUEST(); + CLAIM_REQUEST(), + + GET_FIRMWARE_REQUEST; private final boolean requiresRulesProcessing; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index aa4236ccf1..99d4cfb772 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -20,6 +20,7 @@ import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; @@ -28,7 +29,6 @@ import org.eclipse.californium.core.observe.ObserveRelation; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.core.server.resources.ResourceObserver; -import org.springframework.util.StringUtils; import org.thingsboard.server.coapserver.CoapServerService; import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; import org.thingsboard.server.common.data.DataConstants; @@ -120,6 +120,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { processExchangeGetRequest(exchange, featureType.get()); } else if (featureType.get() == FeatureType.ATTRIBUTES) { processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); + } else if (featureType.get() == FeatureType.FIRMWARE) { + processRequest(exchange, SessionMsgType.GET_FIRMWARE_REQUEST); } else { log.trace("Invalid feature type parameter"); exchange.respond(CoAP.ResponseCode.BAD_REQUEST); @@ -201,7 +203,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { Request request = advanced.getRequest(); String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY); - if (!StringUtils.isEmpty(dtlsSessionIdStr)) { + if (StringUtils.isNotEmpty(dtlsSessionIdStr)) { if (dtlsSessionIdMap != null) { TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> { @@ -323,6 +325,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource { coapTransportAdaptor.convertToGetAttributes(sessionId, request), new CoapNoOpCallback(exchange)); break; + case GET_FIRMWARE_REQUEST: + TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build(); + transportContext.getTransportService().process(sessionInfo, requestMsg, new FirmwareCallback(exchange)); + break; } } catch (AdaptorException e) { log.trace("[{}] Failed to decode message: ", sessionId, e); @@ -424,6 +434,40 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } + private class FirmwareCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + FirmwareCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(TransportProtos.GetFirmwareResponseMsg msg) { + String title = exchange.getQueryParameter("title"); + String version = exchange.getQueryParameter("version"); + if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { + if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) { + String firmwareId = new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB()).toString(); + String strChunkSize = exchange.getQueryParameter("chunkSize"); + String strChunk = exchange.getQueryParameter("chunk"); + int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize); + int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk); + exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getFirmwareCacheReader().get(firmwareId, chunkSize, chunk)); + } else { + exchange.respond(CoAP.ResponseCode.BAD_REQUEST); + } + } else { + exchange.respond(CoAP.ResponseCode.NOT_FOUND); + } + } + + @Override + public void onError(Throwable e) { + log.warn("Failed to process request", e); + exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + } + } + private static class CoapSessionListener implements SessionMsgListener { private final CoapExchange exchange; diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index af4cb021e1..a165cde74b 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -18,7 +18,6 @@ package org.thingsboard.server.transport.coap; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.CoapResource; import org.eclipse.californium.core.CoapServer; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 6b0ae89670..69fc94141d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -35,6 +35,7 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; @@ -92,6 +93,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private ResourceService resourceService; + @Autowired + private FirmwareService firmwareService; + @Override public Tenant findTenantById(TenantId tenantId) { log.trace("Executing findTenantById [{}]", tenantId); @@ -146,6 +150,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe ruleChainService.deleteRuleChainsByTenantId(tenantId); apiUsageStateService.deleteApiUsageStateByTenantId(tenantId); resourceService.deleteResourcesByTenantId(tenantId); + firmwareService.deleteFirmwaresByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); deleteEntityRelations(tenantId, tenantId); } From aee3fd3b6e477b8b77bc85c58a10e58bffb1ce16 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Apr 2021 11:29:04 +0300 Subject: [PATCH 084/127] FirmwareCache refactoring --- .../transport/DefaultTransportApiService.java | 12 +++--- .../firmware/AbstractRedisFirmwareCache.java | 33 ---------------- ...Reader.java => CaffeineFirmwareCache.java} | 22 +++++++---- .../firmware/CaffeineFirmwareCacheWriter.java | 38 ------------------- .../cache/firmware/FirmwareCacheWriter.java | 20 ---------- ...acheReader.java => FirmwareDataCache.java} | 7 +++- .../firmware/RedisFirmwareCacheWriter.java | 38 ------------------- ...eader.java => RedisFirmwareDataCache.java} | 31 ++++++++++++--- .../transport/coap/CoapTransportResource.java | 2 +- .../transport/http/DeviceApiController.java | 2 +- .../transport/mqtt/MqttTransportHandler.java | 2 +- .../common/transport/TransportContext.java | 4 +- .../dao/firmware/BaseFirmwareService.java | 20 ++++------ 13 files changed, 63 insertions(+), 168 deletions(-) delete mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java rename common/cache/src/main/java/org/thingsboard/server/cache/firmware/{CaffeineFirmwareCacheReader.java => CaffeineFirmwareCache.java} (77%) delete mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java delete mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java rename common/cache/src/main/java/org/thingsboard/server/cache/firmware/{FirmwareCacheReader.java => FirmwareDataCache.java} (87%) delete mode 100644 common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java rename common/cache/src/main/java/org/thingsboard/server/cache/firmware/{RedisFirmwareCacheReader.java => RedisFirmwareDataCache.java} (61%) 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 058b5090ea..d55ed17357 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 @@ -23,11 +23,10 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.cache.firmware.FirmwareCacheWriter; +import org.thingsboard.server.cache.firmware.FirmwareDataCache; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -61,7 +60,6 @@ import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; @@ -116,7 +114,7 @@ public class DefaultTransportApiService implements TransportApiService { private final DeviceProvisionService deviceProvisionService; private final TbResourceService resourceService; private final FirmwareService firmwareService; - private final FirmwareCacheWriter firmwareCacheWriter; + private final FirmwareDataCache firmwareDataCache; private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); @@ -125,7 +123,7 @@ public class DefaultTransportApiService implements TransportApiService { RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, - DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareCacheWriter firmwareCacheWriter) { + DeviceProvisionService deviceProvisionService, TbResourceService resourceService, FirmwareService firmwareService, FirmwareDataCache firmwareDataCache) { this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; this.apiUsageStateService = apiUsageStateService; @@ -139,7 +137,7 @@ public class DefaultTransportApiService implements TransportApiService { this.deviceProvisionService = deviceProvisionService; this.resourceService = resourceService; this.firmwareService = firmwareService; - this.firmwareCacheWriter = firmwareCacheWriter; + this.firmwareDataCache = firmwareDataCache; } @Override @@ -485,7 +483,7 @@ public class DefaultTransportApiService implements TransportApiService { builder.setVersion(firmware.getVersion()); builder.setFileName(firmware.getFileName()); builder.setContentType(firmware.getContentType()); - firmwareCacheWriter.put(firmwareId.toString(), firmware.getData().array()); + firmwareDataCache.put(firmwareId.toString(), firmware.getData().array()); } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java deleted file mode 100644 index 371b85b94d..0000000000 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/AbstractRedisFirmwareCache.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright © 2016-2021 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.cache.firmware; - -import org.springframework.data.redis.connection.RedisConnectionFactory; - -import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; - -public abstract class AbstractRedisFirmwareCache { - - protected final RedisConnectionFactory redisConnectionFactory; - - protected AbstractRedisFirmwareCache(RedisConnectionFactory redisConnectionFactory) { - this.redisConnectionFactory = redisConnectionFactory; - } - - protected byte[] toFirmwareCacheKey(String key) { - return String.format("%s::%s", FIRMWARE_CACHE, key).getBytes(); - } -} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCache.java similarity index 77% rename from common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java rename to common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCache.java index ac53027628..1acb09b28e 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheReader.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCache.java @@ -15,22 +15,20 @@ */ package org.thingsboard.server.cache.firmware; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; @Service -@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='null')") -public class CaffeineFirmwareCacheReader implements FirmwareCacheReader { +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@RequiredArgsConstructor +public class CaffeineFirmwareCache implements FirmwareDataCache { private final CacheManager cacheManager; - public CaffeineFirmwareCacheReader(CacheManager cacheManager) { - this.cacheManager = cacheManager; - } - @Override public byte[] get(String key) { return get(key, 0, 0); @@ -57,4 +55,14 @@ public class CaffeineFirmwareCacheReader implements FirmwareCacheReader { } return new byte[0]; } + + @Override + public void put(String key, byte[] value) { + cacheManager.getCache(FIRMWARE_CACHE).putIfAbsent(key, value); + } + + @Override + public void evict(String key) { + cacheManager.getCache(FIRMWARE_CACHE).evict(key); + } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java deleted file mode 100644 index bba4c827bb..0000000000 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/CaffeineFirmwareCacheWriter.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright © 2016-2021 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.cache.firmware; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.cache.CacheManager; -import org.springframework.stereotype.Service; - -import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; - -@Service -@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && ('${cache.type:null}'=='caffeine' || '${cache.type:null}'=='null')") -public class CaffeineFirmwareCacheWriter implements FirmwareCacheWriter { - - private final CacheManager cacheManager; - - public CaffeineFirmwareCacheWriter(CacheManager cacheManager) { - this.cacheManager = cacheManager; - } - - @Override - public void put(String key, byte[] value) { - cacheManager.getCache(FIRMWARE_CACHE).putIfAbsent(key, value); - } -} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java deleted file mode 100644 index f1387f9ff1..0000000000 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheWriter.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright © 2016-2021 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.cache.firmware; - -public interface FirmwareCacheWriter { - void put(String key, byte[] value); -} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java similarity index 87% rename from common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java rename to common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java index 4b4dab4caa..2883691459 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareCacheReader.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java @@ -15,8 +15,13 @@ */ package org.thingsboard.server.cache.firmware; -public interface FirmwareCacheReader { +public interface FirmwareDataCache { + byte[] get(String key); byte[] get(String key, int chunkSize, int chunk); + + void put(String key, byte[] value); + + void evict(String key); } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java deleted file mode 100644 index 7bbb6045e9..0000000000 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheWriter.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright © 2016-2021 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.cache.firmware; - -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.stereotype.Service; - -@Service -@ConditionalOnExpression("('${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core') && '${cache.type:null}'=='redis'") -public class RedisFirmwareCacheWriter extends AbstractRedisFirmwareCache implements FirmwareCacheWriter { - - public RedisFirmwareCacheWriter(RedisConnectionFactory redisConnectionFactory) { - super(redisConnectionFactory); - } - - @Override - public void put(String key, byte[] value) { - try (RedisConnection connection = redisConnectionFactory.getConnection()) { - connection.set(toFirmwareCacheKey(key), value); - } - } - -} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareDataCache.java similarity index 61% rename from common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java rename to common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareDataCache.java index 9bf8b40230..08dd6facc2 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareCacheReader.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/RedisFirmwareDataCache.java @@ -15,18 +15,20 @@ */ package org.thingsboard.server.cache.firmware; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.data.redis.connection.RedisConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.stereotype.Service; +import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; + @Service -@ConditionalOnExpression("(('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true') || '${service.type:null}'=='tb-transport') && '${cache.type:null}'=='redis'") -public class RedisFirmwareCacheReader extends AbstractRedisFirmwareCache implements FirmwareCacheReader { +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@RequiredArgsConstructor +public class RedisFirmwareDataCache implements FirmwareDataCache { - public RedisFirmwareCacheReader(RedisConnectionFactory redisConnectionFactory) { - super(redisConnectionFactory); - } + private final RedisConnectionFactory redisConnectionFactory; @Override public byte[] get(String key) { @@ -46,4 +48,21 @@ public class RedisFirmwareCacheReader extends AbstractRedisFirmwareCache impleme } } + @Override + public void put(String key, byte[] value) { + try (RedisConnection connection = redisConnectionFactory.getConnection()) { + connection.set(toFirmwareCacheKey(key), value); + } + } + + @Override + public void evict(String key) { + try (RedisConnection connection = redisConnectionFactory.getConnection()) { + connection.del(toFirmwareCacheKey(key)); + } + } + + private byte[] toFirmwareCacheKey(String key) { + return String.format("%s::%s", FIRMWARE_CACHE, key).getBytes(); + } } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 99d4cfb772..78ea5f3df0 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -452,7 +452,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { String strChunk = exchange.getQueryParameter("chunk"); int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize); int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk); - exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getFirmwareCacheReader().get(firmwareId, chunkSize, chunk)); + exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getFirmwareDataCache().get(firmwareId, chunkSize, chunk)); } else { exchange.respond(CoAP.ResponseCode.BAD_REQUEST); } diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index dc3b980f88..62b46a5fe7 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -299,7 +299,7 @@ public class DeviceApiController { responseWriter.setResult(new ResponseEntity<>(HttpStatus.NOT_FOUND)); } else if (title.equals(firmwareResponseMsg.getTitle()) && version.equals(firmwareResponseMsg.getVersion())) { String firmwareId = new UUID(firmwareResponseMsg.getFirmwareIdMSB(), firmwareResponseMsg.getFirmwareIdLSB()).toString(); - ByteArrayResource resource = new ByteArrayResource(transportContext.getFirmwareCacheReader().get(firmwareId, chuckSize, chuck)); + ByteArrayResource resource = new ByteArrayResource(transportContext.getFirmwareDataCache().get(firmwareId, chuckSize, chuck)); ResponseEntity response = ResponseEntity.ok() .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + firmwareResponseMsg.getFileName()) .header("x-filename", firmwareResponseMsg.getFileName()) 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 73ca2cdf9d..5e4d8a75b9 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 @@ -452,7 +452,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId); ack(ctx, msgId); try { - byte[] firmwareChunk = context.getFirmwareCacheReader().get(firmwareId, chunkSize, chunk); + byte[] firmwareChunk = context.getFirmwareDataCache().get(firmwareId, chunkSize, chunk); deviceSessionCtx.getPayloadAdaptor() .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk) .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index a1d09b78a7..6273c1f155 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -20,7 +20,7 @@ import lombok.Data; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.thingsboard.server.cache.firmware.FirmwareCacheReader; +import org.thingsboard.server.cache.firmware.FirmwareDataCache; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.scheduler.SchedulerComponent; @@ -53,7 +53,7 @@ public abstract class TransportContext { @Getter @Autowired - private FirmwareCacheReader firmwareCacheReader; + private FirmwareDataCache firmwareDataCache; @PostConstruct public void init() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 7ddd48c2eb..ed7987b23f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.firmware; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -24,6 +25,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.firmware.FirmwareDataCache; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.Tenant; @@ -47,6 +49,7 @@ import static org.thingsboard.server.dao.service.Validator.validatePageLink; @Service @Slf4j +@RequiredArgsConstructor public class BaseFirmwareService implements FirmwareService { public static final String INCORRECT_FIRMWARE_ID = "Incorrect firmwareId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; @@ -55,13 +58,7 @@ public class BaseFirmwareService implements FirmwareService { private final FirmwareDao firmwareDao; private final FirmwareInfoDao firmwareInfoDao; private final CacheManager cacheManager; - - public BaseFirmwareService(TenantDao tenantDao, FirmwareDao firmwareDao, FirmwareInfoDao firmwareInfoDao, CacheManager cacheManager) { - this.tenantDao = tenantDao; - this.firmwareDao = firmwareDao; - this.firmwareInfoDao = firmwareInfoDao; - this.cacheManager = cacheManager; - } + private final FirmwareDataCache firmwareDataCache; @Override public FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo) { @@ -72,7 +69,7 @@ public class BaseFirmwareService implements FirmwareService { if (firmwareId != null) { Cache cache = cacheManager.getCache(FIRMWARE_CACHE); cache.evict(toFirmwareInfoKey(firmwareId)); - cache.evict(toFirmwareDataKey(firmwareId)); + firmwareDataCache.evict(firmwareId.toString()); } return firmwareInfoDao.save(firmwareInfo.getTenantId(), firmwareInfo); } catch (Exception t) { @@ -94,7 +91,7 @@ public class BaseFirmwareService implements FirmwareService { if (firmwareId != null) { Cache cache = cacheManager.getCache(FIRMWARE_CACHE); cache.evict(toFirmwareInfoKey(firmwareId)); - cache.evict(toFirmwareDataKey(firmwareId)); + firmwareDataCache.evict(firmwareId.toString()); } return firmwareDao.save(firmware.getTenantId(), firmware); } catch (Exception t) { @@ -145,7 +142,7 @@ public class BaseFirmwareService implements FirmwareService { try { Cache cache = cacheManager.getCache(FIRMWARE_CACHE); cache.evict(toFirmwareInfoKey(firmwareId)); - cache.evict(toFirmwareDataKey(firmwareId)); + firmwareDataCache.evict(firmwareId.toString()); firmwareDao.removeById(tenantId, firmwareId.getId()); } catch (Exception t) { ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); @@ -329,7 +326,4 @@ public class BaseFirmwareService implements FirmwareService { return Collections.singletonList(firmwareId); } - private static String toFirmwareDataKey(FirmwareId firmwareId) { - return firmwareId.toString(); - } } From 83e31f42631f21cb3ac5ef99059d6922dce84b5e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Apr 2021 12:44:57 +0300 Subject: [PATCH 085/127] added firmware type --- .../main/data/upgrade/3.2.2/schema_update.sql | 1 + .../firmware/DefaultFirmwareStateService.java | 46 ++++++++++++++----- .../server/common/data/DataConstants.java | 26 +++++++++-- .../server/common/data/FirmwareInfo.java | 2 + .../server/common/data/FirmwareType.java | 5 ++ common/queue/src/main/proto/queue.proto | 9 ++-- .../server/dao/model/ModelConstants.java | 1 + .../server/dao/model/sql/FirmwareEntity.java | 10 ++++ .../dao/model/sql/FirmwareInfoEntity.java | 12 ++++- .../sql/firmware/FirmwareInfoRepository.java | 4 +- .../resources/sql/schema-entities-hsql.sql | 1 + .../main/resources/sql/schema-entities.sql | 1 + 12 files changed, 95 insertions(+), 23 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/FirmwareType.java diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index e1a2952f64..27c91a93fe 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -63,6 +63,7 @@ CREATE TABLE IF NOT EXISTS firmware ( id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid NOT NULL, + type varchar(32) NOT NULL, title varchar(255) NOT NULL, version varchar(255) NOT NULL, file_name varchar(255), diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index 6e0e3fd246..e0aef22eab 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -22,7 +22,6 @@ import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; 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.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.FirmwareId; @@ -35,7 +34,6 @@ import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; @@ -59,6 +57,11 @@ import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_CHECKSUM import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_SIZE; import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_TITLE; import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_VERSION; +import static org.thingsboard.server.common.data.DataConstants.SOFTWARE_CHECKSUM; +import static org.thingsboard.server.common.data.DataConstants.SOFTWARE_CHECKSUM_ALGORITHM; +import static org.thingsboard.server.common.data.DataConstants.SOFTWARE_SIZE; +import static org.thingsboard.server.common.data.DataConstants.SOFTWARE_TITLE; +import static org.thingsboard.server.common.data.DataConstants.SOFTWARE_VERSION; @Slf4j @Service @@ -185,9 +188,18 @@ public class DefaultFirmwareStateService implements FirmwareStateService { fwStateMsgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), msg), null); List telemetry = new ArrayList<>(); - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle()))); - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion()))); - telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name()))); + switch (firmware.getType()) { + case FIRMWARE: + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name()))); + break; + case SOFTWARE: + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_SOFTWARE_TITLE, firmware.getTitle()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_SOFTWARE_VERSION, firmware.getVersion()))); + telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.SOFTWARE_STATE, FirmwareUpdateStatus.QUEUED.name()))); + break; + } telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { @Override @@ -223,12 +235,23 @@ public class DefaultFirmwareStateService implements FirmwareStateService { List attributes = new ArrayList<>(); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_TITLE, firmware.getTitle()))); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion()))); + switch (firmware.getType()) { + case SOFTWARE: + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_TITLE, firmware.getTitle()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion()))); + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(DataConstants.FIRMWARE_SIZE, firmware.getDataSize()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum()))); + break; + case FIRMWARE: + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(SOFTWARE_TITLE, firmware.getTitle()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(SOFTWARE_VERSION, firmware.getVersion()))); + attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(SOFTWARE_SIZE, firmware.getDataSize()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(SOFTWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm()))); + attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(SOFTWARE_CHECKSUM, firmware.getChecksum()))); + break; + } - attributes.add(new BaseAttributeKvEntry(ts, new LongDataEntry(FIRMWARE_SIZE, firmware.getDataSize()))); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM_ALGORITHM, firmware.getChecksumAlgorithm()))); - attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum()))); telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { @@ -244,7 +267,8 @@ public class DefaultFirmwareStateService implements FirmwareStateService { private void remove(Device device) { telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, - Arrays.asList(FIRMWARE_TITLE, FIRMWARE_VERSION, FIRMWARE_SIZE, FIRMWARE_CHECKSUM_ALGORITHM, FIRMWARE_CHECKSUM), + Arrays.asList(FIRMWARE_TITLE, FIRMWARE_VERSION, FIRMWARE_SIZE, FIRMWARE_CHECKSUM_ALGORITHM, FIRMWARE_CHECKSUM, + SOFTWARE_TITLE, SOFTWARE_VERSION, SOFTWARE_SIZE, SOFTWARE_CHECKSUM_ALGORITHM, SOFTWARE_CHECKSUM), new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index d53e992db8..d9a8d70d28 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -93,21 +93,37 @@ public class DataConstants { public static final String USERNAME = "username"; public static final String PASSWORD = "password"; + public static final String EDGE_MSG_SOURCE = "edge"; + public static final String MSG_SOURCE_KEY = "source"; + //firmware //telemetry - public static final String CURRENT_FIRMWARE_TITLE = "cur_fw_title"; - public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version"; + public static final String CURRENT_FIRMWARE_TITLE = "current_fw_title"; + public static final String CURRENT_FIRMWARE_VERSION = "current_fw_version"; public static final String TARGET_FIRMWARE_TITLE = "target_fw_title"; public static final String TARGET_FIRMWARE_VERSION = "target_fw_version"; public static final String FIRMWARE_STATE = "fw_state"; //attributes - //telemetry public static final String FIRMWARE_TITLE = "fw_title"; public static final String FIRMWARE_VERSION = "fw_version"; public static final String FIRMWARE_SIZE = "fw_size"; public static final String FIRMWARE_CHECKSUM = "fw_checksum"; public static final String FIRMWARE_CHECKSUM_ALGORITHM = "fw_checksum_algorithm"; - public static final String EDGE_MSG_SOURCE = "edge"; - public static final String MSG_SOURCE_KEY = "source"; + + //software + //telemetry + public static final String CURRENT_SOFTWARE_TITLE = "current_sw_title"; + public static final String CURRENT_SOFTWARE_VERSION = "current_sw_version"; + public static final String TARGET_SOFTWARE_TITLE = "target_sw_title"; + public static final String TARGET_SOFTWARE_VERSION = "target_sw_version"; + public static final String SOFTWARE_STATE = "sw_state"; + + //attributes + public static final String SOFTWARE_TITLE = "sw_title"; + public static final String SOFTWARE_VERSION = "sw_version"; + public static final String SOFTWARE_SIZE = "sw_size"; + public static final String SOFTWARE_CHECKSUM = "sw_checksum"; + public static final String SOFTWARE_CHECKSUM_ALGORITHM = "sw_checksum_algorithm"; + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java index 3a26763c4f..56fa319466 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -29,6 +29,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo private static final long serialVersionUID = 3168391583570815419L; private TenantId tenantId; + private FirmwareType type; private String title; private String version; private boolean hasData; @@ -50,6 +51,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo public FirmwareInfo(FirmwareInfo firmwareInfo) { super(firmwareInfo); this.tenantId = firmwareInfo.getTenantId(); + this.type = firmwareInfo.getType(); this.title = firmwareInfo.getTitle(); this.version = firmwareInfo.getVersion(); this.hasData = firmwareInfo.isHasData(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareType.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareType.java new file mode 100644 index 0000000000..be7afe9da2 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareType.java @@ -0,0 +1,5 @@ +package org.thingsboard.server.common.data; + +public enum FirmwareType { + FIRMWARE, SOFTWARE +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 77826ea807..c1857e8933 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -363,10 +363,11 @@ message GetFirmwareResponseMsg { ResponseStatus responseStatus = 1; int64 firmwareIdMSB = 2; int64 firmwareIdLSB = 3; - string title = 4; - string version = 5; - string contentType = 6; - string fileName = 7; + string type = 4; + string title = 5; + string version = 6; + string contentType = 7; + string fileName = 8; } //Used to report session state to tb-Service and persist this state in the cache on the tb-Service level. 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 ae3b974054..260c8aa743 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 @@ -476,6 +476,7 @@ public class ModelConstants { */ public static final String FIRMWARE_TABLE_NAME = "firmware"; public static final String FIRMWARE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; + public static final String FIRMWARE_TYPE_COLUMN = "type"; public static final String FIRMWARE_TITLE_COLUMN = TITLE_PROPERTY; public static final String FIRMWARE_VERSION_COLUMN = "version"; public static final String FIRMWARE_FILE_NAME_COLUMN = "file_name"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java index ccfa0229bd..267a7319ec 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -21,6 +21,7 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.Firmware; +import org.thingsboard.server.common.data.FirmwareType; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -30,6 +31,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Table; import java.nio.ByteBuffer; import java.util.UUID; @@ -43,6 +46,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @@ -56,6 +60,10 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex @Column(name = FIRMWARE_TENANT_ID_COLUMN) private UUID tenantId; + @Enumerated(EnumType.STRING) + @Column(name = FIRMWARE_TYPE_COLUMN) + private FirmwareType type; + @Column(name = FIRMWARE_TITLE_COLUMN) private String title; @@ -95,6 +103,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex this.createdTime = firmware.getCreatedTime(); this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); + this.type = firmware.getType(); this.title = firmware.getTitle(); this.version = firmware.getVersion(); this.fileName = firmware.getFileName(); @@ -121,6 +130,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex Firmware firmware = new Firmware(new FirmwareId(id)); firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); + firmware.setType(type); firmware.setTitle(title); firmware.setVersion(version); firmware.setFileName(fileName); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java index 3549291e4b..f7f5c6329d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java @@ -22,6 +22,7 @@ import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.FirmwareType; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -31,6 +32,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Table; import javax.persistence.Transient; import java.util.UUID; @@ -45,6 +48,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_HAS_DATA_ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TITLE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_VERSION_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY; @@ -58,6 +62,10 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Column(name = FIRMWARE_TENANT_ID_COLUMN) private UUID tenantId; + @Enumerated(EnumType.STRING) + @Column(name = FIRMWARE_TYPE_COLUMN) + private FirmwareType type; + @Column(name = FIRMWARE_TITLE_COLUMN) private String title; @@ -107,12 +115,13 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.additionalInfo = firmware.getAdditionalInfo(); } - public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version, + public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String type, String title, String version, String fileName, String contentType, String checksumAlgorithm, String checksum, Long dataSize, Object additionalInfo, boolean hasData) { this.id = id; this.createdTime = createdTime; this.tenantId = tenantId; + this.type = FirmwareType.valueOf(type); this.title = title; this.version = version; this.fileName = fileName; @@ -139,6 +148,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S FirmwareInfo firmware = new FirmwareInfo(new FirmwareId(id)); firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); + firmware.setType(type); firmware.setTitle(title); firmware.setVersion(version); firmware.setFileName(fileName); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java index 5601019c13..458cecf63d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java @@ -25,14 +25,14 @@ import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity; import java.util.UUID; public interface FirmwareInfoRepository extends CrudRepository { - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + "f.tenantId = :tenantId " + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") Page findAllByTenantId(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + "f.tenantId = :tenantId " + "AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 6dffac26b0..d9bc1003dc 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -162,6 +162,7 @@ CREATE TABLE IF NOT EXISTS firmware ( id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid NOT NULL, + type varchar(32) NOT NULL, title varchar(255) NOT NULL, version varchar(255) NOT NULL, file_name varchar(255), diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 7d7a18abc6..0de1bfab7b 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -180,6 +180,7 @@ CREATE TABLE IF NOT EXISTS firmware ( id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid NOT NULL, + type varchar(32) NOT NULL, title varchar(255) NOT NULL, version varchar(255) NOT NULL, file_name varchar(255), From 5429175db95973fe1cea19f29939bedfd603941c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 27 Apr 2021 08:53:54 +0300 Subject: [PATCH 086/127] added target_firmware_ts timeseries for the firmware --- .../server/service/firmware/DefaultFirmwareStateService.java | 1 + .../java/org/thingsboard/server/common/data/DataConstants.java | 1 + 2 files changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index 6e0e3fd246..e452626af1 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -187,6 +187,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { List telemetry = new ArrayList<>(); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_TITLE, firmware.getTitle()))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.TARGET_FIRMWARE_VERSION, firmware.getVersion()))); + telemetry.add(new BasicTsKvEntry(ts, new LongDataEntry(DataConstants.TARGET_FIRMWARE_TS, ts))); telemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_STATE, FirmwareUpdateStatus.QUEUED.name()))); telemetryService.saveAndNotify(tenantId, deviceId, telemetry, new FutureCallback<>() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index d53e992db8..d0fdf169fc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -99,6 +99,7 @@ public class DataConstants { public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version"; public static final String TARGET_FIRMWARE_TITLE = "target_fw_title"; public static final String TARGET_FIRMWARE_VERSION = "target_fw_version"; + public static final String TARGET_FIRMWARE_TS = "target_fw_ts"; public static final String FIRMWARE_STATE = "fw_state"; //attributes From ce24a87bed136bda9137c63f3c599fc4018c1424 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 27 Apr 2021 10:22:05 +0300 Subject: [PATCH 087/127] Add firmware dashboard to loadDemo --- .../data/json/demo/dashboards/firmware.json | 2385 +++++++++++++++++ 1 file changed, 2385 insertions(+) create mode 100644 application/src/main/data/json/demo/dashboards/firmware.json diff --git a/application/src/main/data/json/demo/dashboards/firmware.json b/application/src/main/data/json/demo/dashboards/firmware.json new file mode 100644 index 0000000000..f7dfd7e380 --- /dev/null +++ b/application/src/main/data/json/demo/dashboards/firmware.json @@ -0,0 +1,2385 @@ +{ + "title": "Firmware", + "configuration": { + "description": "", + "widgets": { + "cd03188e-cd9d-9601-fd57-da4cb95fc016": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "timewindow": { + "realtime": { + "interval": 1000, + "timewindowMs": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 200 + } + }, + "showTitle": true, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "4px", + "settings": { + "enableSearch": true, + "displayPagination": true, + "defaultPageSize": 10, + "defaultSortOrder": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": false, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "8fdb88d0-50ac-2232-fdb7-69c30c16544e", + "dataKeys": [ + { + "name": "current_fw_title", + "type": "timeseries", + "label": "Current FW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_fw_version", + "type": "timeseries", + "label": "Current FW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_title", + "type": "timeseries", + "label": "Target FW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_version", + "type": "timeseries", + "label": "Target FW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_ts", + "type": "timeseries", + "label": "Target FW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_checksum", + "type": "attribute", + "label": "fw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History firmware update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_firmware_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit firmware", + "icon": "edit", + "type": "customPretty", + "customHtml": "\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n", + "customCss": "", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download firware", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "cd03188e-cd9d-9601-fd57-da4cb95fc016" + }, + "100b756c-0082-6505-3ae1-3603e6deea48": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "timeseries_table", + "type": "timeseries", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 8, + "sizeY": 6.5, + "config": { + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "19f41c21-d9af-e666-8f50-e1748778f955", + "filterId": null, + "dataKeys": [ + { + "name": "current_fw_title", + "type": "timeseries", + "label": "Current firmware title", + "color": "#2196f3", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.5978079905579401, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_fw_version", + "type": "timeseries", + "label": "Current firmware version", + "color": "#4caf50", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.027392025058568192, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_title", + "type": "timeseries", + "label": "Target firmware title", + "color": "#f44336", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.9496350796287059, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_version", + "type": "timeseries", + "label": "Target firmware version", + "color": "#ffc107", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.6734152252264187, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Status", + "color": "#607d8b", + "settings": { + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "" + }, + "_hash": 0.2983399718643074, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": true, + "postFuncBody": "function capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\nif (value !== '') {\n return capitalize(value);\n}\nreturn value;" + } + ] + } + ], + "timewindow": { + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "timewindowMs": 2592000000, + "quickInterval": "CURRENT_DAY", + "interval": 1000 + }, + "aggregation": { + "type": "NONE", + "limit": 200 + } + }, + "showTitle": false, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "8px", + "settings": { + "showTimestamp": true, + "displayPagination": true, + "defaultPageSize": 10, + "enableSearch": true, + "enableStickyHeader": true, + "enableStickyAction": true + }, + "title": "Firmware history", + "dropShadow": false, + "enableFullscreen": false, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "widgetStyle": {}, + "actions": {}, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "displayTimewindow": true, + "titleTooltip": "" + }, + "row": 0, + "col": 0, + "id": "100b756c-0082-6505-3ae1-3603e6deea48" + }, + "17543c57-af4a-2c1e-bf12-53a7b46791e6": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 8, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entityCount", + "name": "", + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", + "dataKeys": [ + { + "name": "count", + "type": "count", + "label": "waitingDevicesNumber", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7404827038869322, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "cardHtml": "
\n
\n \n
\n ${waitingDevicesNumber:0}\n
\n
\n Device Waiting\n
\n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" + }, + "title": "New HTML Value Card", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "activeDevices", + "icon": "more_horiz", + "type": "openDashboardState", + "targetDashboardStateId": "device_waiting", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "4d9a77a2-f0a5-690c-a83b-b0e940be788c" + } + ] + }, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "enableDataExport": false, + "displayTimewindow": true + }, + "id": "17543c57-af4a-2c1e-bf12-53a7b46791e6" + }, + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 8, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entityCount", + "name": "", + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897", + "dataKeys": [ + { + "name": "count", + "type": "count", + "label": "updatingDevicesNumber", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7404827038869322, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "cardHtml": "
\n
\n \n
\n ${updatingDevicesNumber:0}\n
\n
\n Device Updating\n
\n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" + }, + "title": "New HTML Value Card", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "activeDevices", + "icon": "more_horiz", + "type": "openDashboardState", + "targetDashboardStateId": "device_updating", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "57d39904-2350-b29b-78ed-56b8268814cb" + } + ] + }, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "enableDataExport": false, + "displayTimewindow": true + }, + "id": "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6" + }, + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 8, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entityCount", + "name": "", + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "6044e198-df64-cd76-f339-696f220c4943", + "dataKeys": [ + { + "name": "count", + "type": "count", + "label": "updatedDevicesNumber", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7404827038869322, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "cardHtml": "
\n
\n \n
\n ${updatedDevicesNumber:0}\n
\n
\n Device Updated\n
\n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .content img {\n height: 28px; \n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" + }, + "title": "New HTML Value Card", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "activeDevices", + "icon": "more_horiz", + "type": "openDashboardState", + "targetDashboardStateId": "device_updated", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "d787c212-8c56-34f0-349a-5aae2ffd1eae" + } + ] + }, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "enableDataExport": false, + "displayTimewindow": true + }, + "id": "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81" + }, + "77b10144-b904-edd5-8c7c-8fb75616c6d8": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "html_value_card", + "type": "latest", + "title": "New widget", + "sizeX": 8, + "sizeY": 3, + "config": { + "datasources": [ + { + "type": "entityCount", + "name": "", + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", + "dataKeys": [ + { + "name": "count", + "type": "count", + "label": "updatingDevicesNumber", + "color": "#4caf50", + "settings": {}, + "_hash": 0.7404827038869322, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "timewindow": { + "realtime": { + "timewindowMs": 60000 + } + }, + "showTitle": false, + "backgroundColor": "#fff", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "0px", + "settings": { + "cardHtml": "
\n
\n
\n \n \n \n
\n
\n ${updatingDevicesNumber:0}\n
\n \n
\n Device Failed\n
\n
\n
", + "cardCss": ".card {\n width: 100%;\n height: 100%;\n border: 1px solid #E0E0E0;\n box-sizing: border-box;\n}\n\n.card .content {\n padding: 20px 10px;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n height: 100%;\n box-sizing: border-box;\n}\n\n.card .container-svg {\n height: 40px;\n width: 40px;\n}\n\n.card .value {\n margin: 18px 0 5px;\n font-weight: 500;\n font-size: 3em;\n line-height: 1.1em;\n text-align: center;\n letter-spacing: -0.02em;\n color: #333333;\n}\n\n.card .description {\n font-size: 1em;\n line-height: 1.1em;\n color: #000000;\n opacity: 0.6;\n text-align: center;\n letter-spacing: -0.02em;\n}\n\n@media (min-width: 960px) and (max-width: 1200px) {\n .card .container-svg {\n height: 28px;\n width: 28px;\n }\n \n .card .value {\n margin: 12px 0 5px;\n font-size: 2em;\n line-height: 1;\n }\n \n .card .description {\n font-size: 0.8em;\n line-height: 1;\n }\n}" + }, + "title": "New HTML Value Card", + "dropShadow": true, + "enableFullscreen": false, + "widgetStyle": {}, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400 + }, + "useDashboardTimewindow": true, + "showLegend": false, + "actions": { + "elementClick": [ + { + "name": "activeDevices", + "icon": "more_horiz", + "type": "openDashboardState", + "targetDashboardStateId": "device_error", + "setEntityId": false, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "0b3d2887-9929-84d5-3795-0763dca15cba" + } + ] + }, + "showTitleIcon": false, + "titleIcon": null, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "enableDataExport": false, + "displayTimewindow": true + }, + "id": "77b10144-b904-edd5-8c7c-8fb75616c6d8" + }, + "21be08bb-ec90-f760-ad6f-e7678f12c401": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "timewindow": { + "realtime": { + "interval": 1000, + "timewindowMs": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 200 + } + }, + "showTitle": true, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "4px", + "settings": { + "enableSearch": true, + "displayPagination": true, + "defaultPageSize": 10, + "defaultSortOrder": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": true, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", + "dataKeys": [ + { + "name": "current_fw_title", + "type": "timeseries", + "label": "Current FW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_fw_version", + "type": "timeseries", + "label": "Current FW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_title", + "type": "timeseries", + "label": "Target FW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_version", + "type": "timeseries", + "label": "Target FW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_ts", + "type": "timeseries", + "label": "Target FW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_checksum", + "type": "attribute", + "label": "fw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History firmware update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_firmware_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit firmware", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download firware", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "21be08bb-ec90-f760-ad6f-e7678f12c401" + }, + "e8280043-d3dc-7acb-c2ff-a4522972ff91": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "timewindow": { + "realtime": { + "interval": 1000, + "timewindowMs": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 200 + } + }, + "showTitle": true, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "4px", + "settings": { + "enableSearch": true, + "displayPagination": true, + "defaultPageSize": 10, + "defaultSortOrder": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": true, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "579f0468-9ce9-7e3e-b34c-88dd3de59897", + "dataKeys": [ + { + "name": "current_fw_title", + "type": "timeseries", + "label": "Current FW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_fw_version", + "type": "timeseries", + "label": "Current FW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_title", + "type": "timeseries", + "label": "Target FW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_version", + "type": "timeseries", + "label": "Target FW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_ts", + "type": "timeseries", + "label": "Target FW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_checksum", + "type": "attribute", + "label": "fw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History firmware update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_firmware_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit firmware", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download firware", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "e8280043-d3dc-7acb-c2ff-a4522972ff91" + }, + "3624013b-378c-f110-5eba-ae95c25a4dcc": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "timewindow": { + "realtime": { + "interval": 1000, + "timewindowMs": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 200 + } + }, + "showTitle": true, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "4px", + "settings": { + "enableSearch": true, + "displayPagination": true, + "defaultPageSize": 10, + "defaultSortOrder": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": true, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", + "dataKeys": [ + { + "name": "current_fw_title", + "type": "timeseries", + "label": "Current FW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_fw_version", + "type": "timeseries", + "label": "Current FW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_title", + "type": "timeseries", + "label": "Target FW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_version", + "type": "timeseries", + "label": "Target FW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_ts", + "type": "timeseries", + "label": "Target FW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_checksum", + "type": "attribute", + "label": "fw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History firmware update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_firmware_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit firmware", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download firware", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "3624013b-378c-f110-5eba-ae95c25a4dcc" + }, + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": { + "isSystemType": true, + "bundleAlias": "cards", + "typeAlias": "entities_table", + "type": "latest", + "title": "New widget", + "image": null, + "description": null, + "sizeX": 7.5, + "sizeY": 6.5, + "config": { + "timewindow": { + "realtime": { + "interval": 1000, + "timewindowMs": 86400000 + }, + "aggregation": { + "type": "NONE", + "limit": 200 + } + }, + "showTitle": true, + "backgroundColor": "rgb(255, 255, 255)", + "color": "rgba(0, 0, 0, 0.87)", + "padding": "4px", + "settings": { + "enableSearch": true, + "displayPagination": true, + "defaultPageSize": 10, + "defaultSortOrder": "entityLabel", + "displayEntityName": false, + "displayEntityType": false, + "enableSelectColumnDisplay": false, + "enableStickyHeader": true, + "enableStickyAction": true, + "entitiesTitle": "Devices", + "displayEntityLabel": true, + "entityLabelColumnTitle": "Device" + }, + "title": "New Entities table", + "dropShadow": true, + "enableFullscreen": true, + "titleStyle": { + "fontSize": "16px", + "fontWeight": 400, + "padding": "5px 10px 5px 10px" + }, + "useDashboardTimewindow": false, + "showLegend": false, + "datasources": [ + { + "type": "entity", + "name": null, + "entityAliasId": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "filterId": "6044e198-df64-cd76-f339-696f220c4943", + "dataKeys": [ + { + "name": "current_fw_title", + "type": "timeseries", + "label": "Current FW title", + "color": "#2196f3", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.09545533885166413, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "current_fw_version", + "type": "timeseries", + "label": "Current FW version", + "color": "#4caf50", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.7206056602328659, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_title", + "type": "timeseries", + "label": "Target FW title", + "color": "#ffc107", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.9934225682766313, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_version", + "type": "timeseries", + "label": "Target FW version", + "color": "#607d8b", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "cellContentFunction": "", + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled" + }, + "_hash": 0.5251724416842531, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "target_fw_ts", + "type": "timeseries", + "label": "Target FW set time", + "color": "#e91e63", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellContentFunction": "if (value !== '') {\n return ctx.date.transform(value, 'yyyy-MM-dd HH:mm:ss');\n}\nreturn '';" + }, + "_hash": 0.31823244858578237, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Progress", + "color": "#9c27b0", + "settings": { + "columnWidth": "30%", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "return {\n 'padding-right': '30px'\n}", + "cellContentFunction": "if (value !== '') {\n var mapProgress = {\n 'QUEUED': 0,\n 'INITIATED': 5,\n 'DOWNLOADING': 10,\n 'DOWNLOADED': 55,\n 'VERIFIED': 60,\n 'UPDATING': 70,\n 'FAILED': 99,\n 'UPDATED': 100\n }\n var color = 'mat-primary';\n var progress = mapProgress[value];\n if (value == 'FAILED') {\n color = 'mat-accent';\n }\n return `
`;\n}" + }, + "_hash": 0.8174211757846257, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_state", + "type": "timeseries", + "label": "Status", + "color": "#f44336", + "settings": { + "columnWidth": "130px", + "useCellStyleFunction": true, + "useCellContentFunction": true, + "defaultColumnVisibility": "visible", + "columnSelectionToDisplay": "enabled", + "cellStyleFunction": "if (value == 'FAILED') {\n return {'color' : '#D93025'};\n}\nreturn {};", + "cellContentFunction": "function icon(value) {\n if (value == 'QUEUED') {\n return '';\n }\n if (value == 'INITIATED' || value == 'DOWNLOADING' || value == 'DOWNLOADED') {\n return '';\n }\n if (value == 'VERIFIED' || value == 'UPDATING' ) {\n return 'update';\n }\n if (value == 'UPDATED') {\n return '';\n }\n if (value == 'FAILED') {\n return 'warning';\n }\n return '';\n}\nfunction capitalize (s) {\n if (typeof s !== 'string') return '';\n return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();\n}\n\nreturn icon(value) + '' + capitalize(value) + '';" + }, + "_hash": 0.7764426948615217, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + }, + { + "name": "fw_checksum", + "type": "attribute", + "label": "fw_checksum", + "color": "#3f51b5", + "settings": { + "columnWidth": "0px", + "useCellStyleFunction": false, + "cellStyleFunction": "", + "useCellContentFunction": false, + "defaultColumnVisibility": "hidden", + "columnSelectionToDisplay": "disabled" + }, + "_hash": 0.5594087842471693, + "units": null, + "decimals": null, + "funcBody": null, + "usePostProcessing": null, + "postFuncBody": null + } + ] + } + ], + "actions": { + "actionCellButton": [ + { + "name": "History firmware update", + "icon": "history", + "type": "openDashboardState", + "targetDashboardStateId": "device_firmware_history", + "setEntityId": true, + "stateEntityParamName": null, + "openInSeparateDialog": false, + "dialogTitle": "", + "dialogHideDashboardToolbar": true, + "dialogWidth": null, + "dialogHeight": null, + "openRightLayout": false, + "id": "98a1406c-3301-bc2f-2c5d-d637ce3b663b" + }, + { + "name": "Edit firmware", + "icon": "edit", + "type": "customPretty", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customCss": "", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", + "customResources": [], + "id": "23099c1d-454b-25dc-8bc0-7cf33c21c5d5" + }, + { + "name": "Download firware", + "icon": "file_download", + "type": "custom", + "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet firmwareService = $injector.get(widgetContext.servicesMap.get('firmwareService'));\nlet deviceProfileService = $injector.get(widgetContext.servicesMap.get('deviceProfileService'));\n\ngetDeviceFirmware();\n\nfunction getDeviceFirmware() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n if (data.firmwareId !== null) {\n firmwareService.downloadFirmware(data.firmwareId.id).subscribe(); \n } else {\n deviceProfileService.getDeviceProfile(data.deviceProfileId.id).subscribe(\n function (deviceProfile) {\n if (deviceProfile.firmwareId !== null) {\n firmwareService.downloadFirmware(deviceProfile.firmwareId.id).subscribe();\n } else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n\n }\n });\n }\n }\n );\n}", + "id": "12533058-42f6-e75f-620c-219c48d01ec0" + }, + { + "name": "Copy checksum", + "icon": "content_copy", + "type": "custom", + "customFunction": "function copyToClipboard(text) {\n if (window.clipboardData && window.clipboardData.setData) {\n return window.clipboardData.setData(\"Text\", text);\n }\n else if (document.queryCommandSupported && document.queryCommandSupported(\"copy\")) {\n var textarea = document.createElement(\"textarea\");\n textarea.textContent = text;\n textarea.style.position = \"fixed\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n return document.execCommand(\"copy\");\n }\n catch (ex) {\n console.warn(\"Copy to clipboard failed.\", ex);\n return false;\n }\n document.body.removeChild(textarea);\n }\n}\nvar entityIdValue = entityId.id;\nvar data = widgetContext.data.find((el) => el.datasource.entityId === entityIdValue && el.dataKey.name === 'fw_checksum');\nvar checksum = data.data[0][1];\nif (checksum !== '') {\n copyToClipboard(checksum);\n widgetContext.showSuccessToast('Firmware checksum has been copied to clipboard', 2000, 'top');\n} else {\n widgetContext.showToast('warn', 'Device ' + entityName +' has not firmware set.', 2000, 'top');\n}", + "id": "09323079-7111-87f7-90d1-c62cd7d85dc7" + } + ] + }, + "showTitleIcon": false, + "iconColor": "rgba(0, 0, 0, 0.87)", + "iconSize": "24px", + "titleTooltip": "", + "widgetStyle": {} + }, + "row": 0, + "col": 0, + "id": "d2d13e0d-4e71-889f-9343-ad2f0af9f176" + } + }, + "states": { + "default": { + "name": "Device list", + "root": true, + "layouts": { + "main": { + "widgets": { + "cd03188e-cd9d-9601-fd57-da4cb95fc016": { + "sizeX": 19, + "sizeY": 12, + "row": 0, + "col": 0 + }, + "17543c57-af4a-2c1e-bf12-53a7b46791e6": { + "sizeX": 5, + "sizeY": 3, + "row": 0, + "col": 19 + }, + "6c1c4e1a-bce0-f5ad-ff8b-ba1dfc5a4ec6": { + "sizeX": 5, + "sizeY": 3, + "row": 3, + "col": 19 + }, + "e6674227-9cf3-a2f6-ecac-5ccfc38a3c81": { + "sizeX": 5, + "sizeY": 3, + "row": 9, + "col": 19 + }, + "77b10144-b904-edd5-8c7c-8fb75616c6d8": { + "sizeX": 5, + "sizeY": 3, + "row": 6, + "col": 19 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 12, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": true, + "mobileRowHeight": 70 + } + } + } + }, + "device_firmware_history": { + "name": "Device Firmware history", + "root": false, + "layouts": { + "main": { + "widgets": { + "100b756c-0082-6505-3ae1-3603e6deea48": { + "sizeX": 24, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "device_waiting": { + "name": "Device waiting", + "root": false, + "layouts": { + "main": { + "widgets": { + "21be08bb-ec90-f760-ad6f-e7678f12c401": { + "sizeX": 24, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "device_updating": { + "name": "Device updating", + "root": false, + "layouts": { + "main": { + "widgets": { + "e8280043-d3dc-7acb-c2ff-a4522972ff91": { + "sizeX": 24, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "device_updated": { + "name": "Device updated", + "root": false, + "layouts": { + "main": { + "widgets": { + "d2d13e0d-4e71-889f-9343-ad2f0af9f176": { + "sizeX": 27, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + }, + "device_error": { + "name": "Device failed", + "root": false, + "layouts": { + "main": { + "widgets": { + "3624013b-378c-f110-5eba-ae95c25a4dcc": { + "sizeX": 24, + "sizeY": 12, + "row": 0, + "col": 0 + } + }, + "gridSettings": { + "backgroundColor": "#eeeeee", + "color": "rgba(0,0,0,0.870588)", + "columns": 24, + "margin": 10, + "backgroundSizeMode": "100%", + "autoFillHeight": true, + "backgroundImageUrl": null, + "mobileAutoFillHeight": false, + "mobileRowHeight": 70 + } + } + } + } + }, + "entityAliases": { + "639da5b4-31f0-0151-6282-c37a3897b7e8": { + "id": "639da5b4-31f0-0151-6282-c37a3897b7e8", + "alias": "All devices", + "filter": { + "type": "entityType", + "resolveMultiple": true, + "entityType": "DEVICE" + } + }, + "19f41c21-d9af-e666-8f50-e1748778f955": { + "id": "19f41c21-d9af-e666-8f50-e1748778f955", + "alias": "State entity", + "filter": { + "type": "stateEntity", + "resolveMultiple": false, + "stateEntityParamName": null, + "defaultStateEntity": null + } + } + }, + "filters": { + "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e": { + "id": "19a0ad1c-b31d-4a29-9d7b-5d87e2a8ea6e", + "filter": "WaitingDevicesFilter", + "keyFilters": [ + { + "key": { + "type": "TIME_SERIES", + "key": "fw_state" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "QUEUED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": true, + "label": "", + "autogeneratedLabel": true, + "order": 0 + } + } + ] + } + ], + "editable": false + }, + "579f0468-9ce9-7e3e-b34c-88dd3de59897": { + "id": "579f0468-9ce9-7e3e-b34c-88dd3de59897", + "filter": "UpdatingDevicesFilter", + "keyFilters": [ + { + "key": { + "type": "TIME_SERIES", + "key": "fw_state" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "OR", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "INITIATED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "fw_state equel", + "autogeneratedLabel": true, + "order": 0 + } + }, + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "DOWNLOADING", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "fw_state equal", + "autogeneratedLabel": true, + "order": 0 + } + }, + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "DOWNLOADED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "fw_state equal", + "autogeneratedLabel": true, + "order": 0 + } + }, + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "VERIFIED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "fw_state equal", + "autogeneratedLabel": true, + "order": 0 + } + }, + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "UPDATING", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": false, + "label": "fw_state equal", + "autogeneratedLabel": true, + "order": 0 + } + } + ], + "type": "COMPLEX" + }, + "userInfo": { + "editable": true, + "label": "", + "autogeneratedLabel": true, + "order": 0 + } + } + ] + } + ], + "editable": false + }, + "6044e198-df64-cd76-f339-696f220c4943": { + "id": "6044e198-df64-cd76-f339-696f220c4943", + "filter": "UpdetedDevicesFilter", + "keyFilters": [ + { + "key": { + "type": "TIME_SERIES", + "key": "fw_state" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "UPDATED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": true, + "label": "", + "autogeneratedLabel": true, + "order": 0 + } + } + ] + } + ], + "editable": false + }, + "bdbc6ea1-95a7-3912-341a-58dc7704a00f": { + "id": "bdbc6ea1-95a7-3912-341a-58dc7704a00f", + "filter": "FailedDevicesFilter", + "keyFilters": [ + { + "key": { + "type": "TIME_SERIES", + "key": "fw_state" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "EQUAL", + "value": { + "defaultValue": "FAILED", + "dynamicValue": null + }, + "ignoreCase": false, + "type": "STRING" + }, + "userInfo": { + "editable": true, + "label": "", + "autogeneratedLabel": true, + "order": 0 + } + } + ] + } + ], + "editable": false + }, + "8fdb88d0-50ac-2232-fdb7-69c30c16544e": { + "id": "8fdb88d0-50ac-2232-fdb7-69c30c16544e", + "filter": "DeviceSearch", + "keyFilters": [ + { + "key": { + "type": "ENTITY_FIELD", + "key": "name" + }, + "valueType": "STRING", + "predicates": [ + { + "keyFilterPredicate": { + "operation": "CONTAINS", + "value": { + "defaultValue": "" + }, + "ignoreCase": true, + "type": "STRING" + }, + "userInfo": { + "editable": true, + "label": "Device name", + "autogeneratedLabel": false, + "order": 0 + } + } + ] + } + ], + "editable": true + } + }, + "timewindow": { + "displayValue": "", + "hideInterval": false, + "hideAggregation": false, + "hideAggInterval": false, + "hideTimezone": false, + "selectedTab": 0, + "realtime": { + "realtimeType": 0, + "interval": 1000, + "timewindowMs": 60000, + "quickInterval": "CURRENT_DAY" + }, + "history": { + "historyType": 0, + "interval": 1000, + "timewindowMs": 60000, + "fixedTimewindow": { + "startTimeMs": 1618998609030, + "endTimeMs": 1619085009030 + }, + "quickInterval": "CURRENT_DAY" + }, + "aggregation": { + "type": "AVG", + "limit": 25000 + } + }, + "settings": { + "stateControllerId": "entity", + "showTitle": false, + "showDashboardsSelect": false, + "showEntitiesSelect": false, + "showDashboardTimewindow": true, + "showDashboardExport": false, + "toolbarAlwaysOpen": true, + "titleColor": "rgba(0,0,0,0.870588)", + "showFilters": true, + "showDashboardLogo": false, + "dashboardLogoUrl": null + } + }, + "name": "Firmware" +} \ No newline at end of file From a5656009d2e6bd83f00d4e84a46f456548d0e95a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 27 Apr 2021 11:47:38 +0300 Subject: [PATCH 088/127] implemented audit logs for the firmware and resource --- .../server/controller/DeviceController.java | 10 +++---- .../server/controller/FirmwareController.java | 25 ++++++++++++----- .../controller/TbResourceController.java | 27 ++++++++++--------- .../src/main/resources/thingsboard.yml | 2 ++ .../server/dao/firmware/FirmwareService.java | 3 +++ .../server/dao/resource/ResourceService.java | 3 +++ .../server/common/data/FirmwareInfo.java | 9 ++++++- .../server/common/data/TbResourceInfo.java | 9 ++++++- .../server/dao/entity/BaseEntityService.java | 20 ++++++++++++-- .../dao/firmware/BaseFirmwareService.java | 7 +++++ .../dao/resource/BaseResourceService.java | 8 ++++++ .../app/shared/models/entity-type.models.ts | 2 ++ .../assets/locale/locale.constant-en_US.json | 4 ++- 13 files changed, 100 insertions(+), 29 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 43bce339dc..378083b873 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -75,7 +75,6 @@ import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.stream.Collectors; import static org.thingsboard.server.controller.EdgeController.EDGE_ID; @@ -120,12 +119,12 @@ public class DeviceController extends BaseController { @ResponseBody public Device saveDevice(@RequestBody Device device, @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException { + boolean created = device.getId() == null; try { device.setTenantId(getCurrentUser().getTenantId()); checkEntity(device.getId(), device, Resource.DEVICE); - boolean created = device.getId() == null; Device oldDevice; if (!created) { oldDevice = deviceService.findDeviceById(getTenantId(), device.getId()); @@ -146,7 +145,7 @@ public class DeviceController extends BaseController { logEntityAction(savedDevice.getId(), savedDevice, savedDevice.getCustomerId(), - device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); + created ? ActionType.ADDED : ActionType.UPDATED, null); if (device.getId() == null) { deviceStateService.onDeviceAdded(savedDevice); @@ -157,10 +156,9 @@ public class DeviceController extends BaseController { firmwareStateService.update(savedDevice, oldDevice); return savedDevice; - } catch ( - Exception e) { + } catch (Exception e) { logEntityAction(emptyId(EntityType.DEVICE), device, - null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); + null, created ? ActionType.ADDED : ActionType.UPDATED, e); throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index c8f10b3d78..a3c067a046 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -30,8 +30,10 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.page.PageData; @@ -101,11 +103,17 @@ public class FirmwareController extends BaseController { @RequestMapping(value = "/firmware", method = RequestMethod.POST) @ResponseBody public FirmwareInfo saveFirmwareInfo(@RequestBody FirmwareInfo firmwareInfo) throws ThingsboardException { - firmwareInfo.setTenantId(getTenantId()); - checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE); + boolean created = firmwareInfo.getId() == null; try { - return firmwareService.saveFirmwareInfo(firmwareInfo); + firmwareInfo.setTenantId(getTenantId()); + checkEntity(firmwareInfo.getId(), firmwareInfo, Resource.FIRMWARE); + FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo); + logEntityAction(savedFirmwareInfo.getId(), savedFirmwareInfo, + null, created ? ActionType.ADDED : ActionType.UPDATED, null); + return savedFirmwareInfo; } catch (Exception e) { + logEntityAction(emptyId(EntityType.FIRMWARE), firmwareInfo, + null, created ? ActionType.ADDED : ActionType.UPDATED, e); throw handleException(e); } } @@ -141,8 +149,11 @@ public class FirmwareController extends BaseController { firmware.setContentType(file.getContentType()); firmware.setData(ByteBuffer.wrap(data)); firmware.setDataSize((long) data.length); - return firmwareService.saveFirmware(firmware); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + logEntityAction(savedFirmware.getId(), savedFirmware, null, ActionType.UPDATED, null); + return savedFirmware; } catch (Exception e) { + logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.UPDATED, e, strFirmwareId); throw handleException(e); } } @@ -183,13 +194,15 @@ public class FirmwareController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @RequestMapping(value = "/firmware/{firmwareId}", method = RequestMethod.DELETE) @ResponseBody - public void deleteResource(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException { + public void deleteFirmware(@PathVariable("firmwareId") String strFirmwareId) throws ThingsboardException { checkParameter(FIRMWARE_ID, strFirmwareId); try { FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); - checkFirmwareInfoId(firmwareId, Operation.DELETE); + FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.DELETE); firmwareService.deleteFirmware(getTenantId(), firmwareId); + logEntityAction(firmwareId, info, null, ActionType.DELETED, null, strFirmwareId); } catch (Exception e) { + logEntityAction(emptyId(EntityType.FIRMWARE), null, null, ActionType.DELETED, e, strFirmwareId); throw handleException(e); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java index b24cf0e76e..97020ca85d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -28,8 +28,10 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; @@ -37,7 +39,6 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -103,12 +104,18 @@ public class TbResourceController extends BaseController { @RequestMapping(value = "/resource", method = RequestMethod.POST) @ResponseBody public TbResource saveResource(@RequestBody TbResource resource) throws ThingsboardException { + boolean created = resource.getId() == null; try { - resource.setTenantId(getTenantId()); - checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); - return addResource(resource); - } - catch (Exception e) { + resource.setTenantId(getTenantId()); + checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); + TbResource savedResource = checkNotNull(resourceService.saveResource(resource)); + tbClusterService.onResourceChange(savedResource, null); + logEntityAction(savedResource.getId(), savedResource, + null, created ? ActionType.ADDED : ActionType.UPDATED, null); + return savedResource; + } catch (Exception e) { + logEntityAction(emptyId(EntityType.TB_RESOURCE), resource, + null, created ? ActionType.ADDED : ActionType.UPDATED, e); throw handleException(e); } } @@ -172,15 +179,11 @@ public class TbResourceController extends BaseController { TbResource tbResource = checkResourceId(resourceId, Operation.DELETE); resourceService.deleteResource(getTenantId(), resourceId); tbClusterService.onResourceDeleted(tbResource, null); + logEntityAction(resourceId, tbResource, null, ActionType.DELETED, null, strResourceId); } catch (Exception e) { + logEntityAction(emptyId(EntityType.TB_RESOURCE), null, null, ActionType.DELETED, e, strResourceId); throw handleException(e); } } - private TbResource addResource(TbResource resource) throws Exception { - checkEntity(resource.getId(), resource, Resource.TB_RESOURCE); - TbResource savedResource = checkNotNull(resourceService.saveResource(resource)); - tbClusterService.onResourceChange(savedResource, null); - return savedResource; - } } \ No newline at end of file diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 270a18e065..e8e1fb15f4 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -496,6 +496,8 @@ audit-log: "entity_view": "${AUDIT_LOG_MASK_ENTITY_VIEW:W}" "device_profile": "${AUDIT_LOG_MASK_DEVICE_PROFILE:W}" "edge": "${AUDIT_LOG_MASK_EDGE:W}" + "tb_resource": "${AUDIT_LOG_MASK_RESOURCE:W}" + "firmware": "${AUDIT_LOG_MASK_FIRMWARE:W}" sink: # Type of external sink. possible options: none, elasticsearch type: "${AUDIT_LOG_SINK_TYPE:none}" diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java index 0fd26fee6e..907638eabb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.firmware; +import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.id.FirmwareId; @@ -32,6 +33,8 @@ public interface FirmwareService { FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId); + ListenableFuture findFirmwareInfoByIdAsync(TenantId tenantId, FirmwareId firmwareId); + PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink); PageData findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java index 68c2c1f5f0..802628ff2c 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.resource; +import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; @@ -34,6 +35,8 @@ public interface ResourceService { TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId); + ListenableFuture findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId); + PageData findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java index 3a26763c4f..c9941e29bd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; @@ -24,7 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; @Slf4j @Data @EqualsAndHashCode(callSuper = true) -public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo implements HasTenantId { +public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId { private static final long serialVersionUID = 3168391583570815419L; @@ -64,4 +65,10 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo public String getSearchText() { return title; } + + @Override + @JsonIgnore + public String getName() { + return title; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java index 95fbcd46ff..9d72d01534 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TbResourceInfo.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data; +import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; @@ -25,7 +26,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @Slf4j @Data @EqualsAndHashCode(callSuper = true) -public class TbResourceInfo extends SearchTextBased implements HasTenantId { +public class TbResourceInfo extends SearchTextBased implements HasName, HasTenantId { private static final long serialVersionUID = 7282664529021651736L; @@ -53,6 +54,12 @@ public class TbResourceInfo extends SearchTextBased implements Has this.searchText = resourceInfo.getSearchText(); } + @Override + @JsonIgnore + public String getName() { + return title; + } + @Override public String getSearchText() { return searchText != null ? searchText : title; diff --git a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 5dc9cf4eed..7e0218740a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -31,7 +31,9 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; @@ -46,6 +48,8 @@ import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.firmware.FirmwareService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; @@ -92,6 +96,12 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe @Autowired private EntityQueryDao entityQueryDao; + @Autowired + private ResourceService resourceService; + + @Autowired + private FirmwareService firmwareService; + @Override public void deleteEntityRelations(TenantId tenantId, EntityId entityId) { super.deleteEntityRelations(tenantId, entityId); @@ -152,6 +162,12 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe case EDGE: hasName = edgeService.findEdgeByIdAsync(tenantId, new EdgeId(entityId.getId())); break; + case TB_RESOURCE: + hasName = resourceService.findResourceInfoByIdAsync(tenantId, new TbResourceId(entityId.getId())); + break; + case FIRMWARE: + hasName = firmwareService.findFirmwareInfoByIdAsync(tenantId, new FirmwareId(entityId.getId())); + break; default: throw new IllegalStateException("Not Implemented!"); } @@ -178,9 +194,9 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe if (pageLink == null) { throw new IncorrectParameterException("Entity Data Page link must be specified."); } else if (pageLink.getPageSize() < 1) { - throw new IncorrectParameterException("Incorrect entity data page link page size '"+pageLink.getPageSize()+"'. Page size must be greater than zero."); + throw new IncorrectParameterException("Incorrect entity data page link page size '" + pageLink.getPageSize() + "'. Page size must be greater than zero."); } else if (pageLink.getPage() < 0) { - throw new IncorrectParameterException("Incorrect entity data page link page '"+pageLink.getPage()+"'. Page must be positive integer."); + throw new IncorrectParameterException("Incorrect entity data page link page '" + pageLink.getPage() + "'. Page must be positive integer."); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 5a80fc75f6..d504845488 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.firmware; import com.google.common.hash.HashFunction; import com.google.common.hash.Hashing; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -115,6 +116,12 @@ public class BaseFirmwareService implements FirmwareService { return firmwareInfoDao.findById(tenantId, firmwareId.getId()); } + @Override + public ListenableFuture findFirmwareInfoByIdAsync(TenantId tenantId, FirmwareId firmwareId) { + log.trace("Executing findFirmwareInfoByIdAsync [{}]", firmwareId); + validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); + return firmwareInfoDao.findByIdAsync(tenantId, firmwareId.getId()); } + @Override public PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findTenantFirmwaresByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index 97675d4b3e..6dacd1357f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.resource; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.hibernate.exception.ConstraintViolationException; @@ -93,6 +94,13 @@ public class BaseResourceService implements ResourceService { return resourceInfoDao.findById(tenantId, resourceId.getId()); } + @Override + public ListenableFuture findResourceInfoByIdAsync(TenantId tenantId, TbResourceId resourceId) { + log.trace("Executing findResourceInfoById [{}] [{}]", tenantId, resourceId); + Validator.validateId(resourceId, INCORRECT_RESOURCE_ID + resourceId); + return resourceInfoDao.findByIdAsync(tenantId, resourceId.getId()); + } + @Override public void deleteResource(TenantId tenantId, TbResourceId resourceId) { log.trace("Executing deleteResource [{}] [{}]", tenantId, resourceId); diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 892d2a1244..ae2382e0ea 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -287,6 +287,7 @@ export const entityTypeTranslations = new Map Date: Tue, 27 Apr 2021 12:40:47 +0300 Subject: [PATCH 089/127] added customer authority for the firmware info methods --- .../thingsboard/server/controller/FirmwareController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index a3c067a046..a38d86ed36 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -73,7 +73,7 @@ public class FirmwareController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/firmware/info/{firmwareId}", method = RequestMethod.GET) @ResponseBody public FirmwareInfo getFirmwareInfoById(@PathVariable(FIRMWARE_ID) String strFirmwareId) throws ThingsboardException { @@ -158,7 +158,7 @@ public class FirmwareController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/firmwares", method = RequestMethod.GET) @ResponseBody public PageData getFirmwares(@RequestParam int pageSize, @@ -174,7 +174,7 @@ public class FirmwareController extends BaseController { } } - @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET) @ResponseBody public PageData getFirmwares(@PathVariable("hasData") boolean hasData, From 04cd86e4bafdef7292d9c332be54ccba98797d12 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 27 Apr 2021 15:05:10 +0300 Subject: [PATCH 090/127] added resource and firmware to the AccessValidator --- .../server/controller/FirmwareController.java | 2 +- .../service/security/AccessValidator.java | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index a38d86ed36..9c08967c8e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -80,7 +80,7 @@ public class FirmwareController extends BaseController { checkParameter(FIRMWARE_ID, strFirmwareId); try { FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); - return checkFirmwareInfoId(firmwareId, Operation.READ); + return checkNotNull(firmwareService.findFirmwareInfoById(getTenantId(), firmwareId)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index 1627002fb1..914b9ce7fa 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -30,6 +30,8 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; @@ -44,8 +46,10 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.rule.RuleChain; @@ -59,6 +63,8 @@ import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.firmware.FirmwareService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; @@ -125,6 +131,12 @@ public class AccessValidator { @Autowired protected ApiUsageStateService apiUsageStateService; + @Autowired + protected ResourceService resourceService; + + @Autowired + protected FirmwareService firmwareService; + private ExecutorService executor; @PostConstruct @@ -217,6 +229,12 @@ public class AccessValidator { case API_USAGE_STATE: validateApiUsageState(currentUser, operation, entityId, callback); return; + case TB_RESOURCE: + validateResource(currentUser, operation, entityId, callback); + return; + case FIRMWARE: + validateFirmware(currentUser, operation, entityId, callback); + return; default: //TODO: add support of other entities throw new IllegalStateException("Not Implemented!"); @@ -282,6 +300,40 @@ public class AccessValidator { } } + private void validateFirmware(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { + if (currentUser.isSystemAdmin()) { + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); + } else { + FirmwareInfo firmware = firmwareService.findFirmwareInfoById(currentUser.getTenantId(), new FirmwareId(entityId.getId())); + if (firmware == null) { + callback.onSuccess(ValidationResult.entityNotFound("Firmware with requested id wasn't found!")); + } else { + try { + accessControlService.checkPermission(currentUser, Resource.FIRMWARE, operation, entityId, firmware); + } catch (ThingsboardException e) { + callback.onSuccess(ValidationResult.accessDenied(e.getMessage())); + } + callback.onSuccess(ValidationResult.ok(firmware)); + } + } + } + + private void validateResource(SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { + ListenableFuture resourceFuture = resourceService.findResourceInfoByIdAsync(currentUser.getTenantId(), new TbResourceId(entityId.getId())); + Futures.addCallback(resourceFuture, getCallback(callback, resource -> { + if (resource == null) { + return ValidationResult.entityNotFound("Resource with requested id wasn't found!"); + } else { + try { + accessControlService.checkPermission(currentUser, Resource.TB_RESOURCE, operation, entityId, resource); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); + } + return ValidationResult.ok(resource); + } + }), executor); + } + private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); From 7edcc605e91a8f5b174bb4ccf651a01c1ffd687b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 28 Apr 2021 13:53:56 +0300 Subject: [PATCH 091/127] Firmware CoAP and transport improvement --- .../transport/DefaultTransportApiService.java | 18 +++++++++++------- .../cache/firmware/FirmwareDataCache.java | 5 +++++ .../transport/coap/CoapTransportResource.java | 4 ++-- .../service/DefaultTransportService.java | 3 +-- 4 files changed, 19 insertions(+), 11 deletions(-) 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 d55ed17357..025122a01b 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 @@ -33,6 +33,7 @@ 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.Firmware; +import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TenantProfile; @@ -471,19 +472,22 @@ public class DefaultTransportApiService implements TransportApiService { if (firmwareId == null) { builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); } else { - Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId); + FirmwareInfo firmwareInfo = firmwareService.findFirmwareInfoById(tenantId, firmwareId); - if (firmware == null) { + if (firmwareInfo == null) { builder.setResponseStatus(TransportProtos.ResponseStatus.NOT_FOUND); } else { builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS); builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits()); builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()); - builder.setTitle(firmware.getTitle()); - builder.setVersion(firmware.getVersion()); - builder.setFileName(firmware.getFileName()); - builder.setContentType(firmware.getContentType()); - firmwareDataCache.put(firmwareId.toString(), firmware.getData().array()); + builder.setTitle(firmwareInfo.getTitle()); + builder.setVersion(firmwareInfo.getVersion()); + builder.setFileName(firmwareInfo.getFileName()); + builder.setContentType(firmwareInfo.getContentType()); + if (!firmwareDataCache.has(firmwareId.toString())) { + Firmware firmware = firmwareService.findFirmwareById(tenantId, firmwareId); + firmwareDataCache.put(firmwareId.toString(), firmware.getData().array()); + } } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java index 2883691459..99f2f0d521 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/firmware/FirmwareDataCache.java @@ -24,4 +24,9 @@ public interface FirmwareDataCache { void put(String key, byte[] value); void evict(String key); + + default boolean has(String firmwareId) { + byte[] data = get(firmwareId, 1, 0); + return data != null && data.length > 0; + } } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 78ea5f3df0..468bc244c6 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -20,7 +20,6 @@ import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; @@ -34,6 +33,7 @@ import org.thingsboard.server.coapserver.TbCoapDtlsSessionInfo; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; @@ -448,7 +448,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) { String firmwareId = new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB()).toString(); - String strChunkSize = exchange.getQueryParameter("chunkSize"); + String strChunkSize = exchange.getQueryParameter("size"); String strChunk = exchange.getQueryParameter("chunk"); int chunkSize = StringUtils.isEmpty(strChunkSize) ? 0 : Integer.parseInt(strChunkSize); int chunk = StringUtils.isEmpty(strChunk) ? 0 : Integer.parseInt(strChunk); 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 1efe4edcea..494ca01903 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 @@ -539,8 +539,7 @@ public class DefaultTransportService implements TransportService { new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setFirmwareRequestMsg(msg).build()); AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), response -> { - TransportProtos.GetFirmwareResponseMsg firmwareResponseMsg = response.getValue().getFirmwareResponseMsg(); - callback.onSuccess(firmwareResponseMsg); + callback.onSuccess(response.getValue().getFirmwareResponseMsg()); }, callback::onError, transportCallbackExecutor); } } From 5976d5172969e0fb11f604686f75bb27a0a7a3eb Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 28 Apr 2021 14:48:17 +0300 Subject: [PATCH 092/127] UI: Add import/export tenant profile --- .../import-export/import-export.service.ts | 51 +++++++++++++++++-- .../tenant-profiles-table-config.resolver.ts | 48 ++++++++++++++++- .../assets/locale/locale.constant-en_US.json | 6 +++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts index 72b5e661e3..3bd63b046b 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts @@ -57,6 +57,8 @@ import { RuleChainService } from '@core/http/rule-chain.service'; import { FiltersInfo } from '@shared/models/query/query.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { DeviceProfile } from '@shared/models/device.models'; +import { TenantProfile } from '@shared/models/tenant.model'; +import { TenantProfileService } from '@core/http/tenant-profile.service'; // @dynamic @Injectable() @@ -70,6 +72,7 @@ export class ImportExportService { private dashboardUtils: DashboardUtilsService, private widgetService: WidgetService, private deviceProfileService: DeviceProfileService, + private tenantProfileService: TenantProfileService, private entityService: EntityService, private ruleChainService: RuleChainService, private utils: UtilsService, @@ -434,7 +437,7 @@ export class ImportExportService { (deviceProfile) => { let name = deviceProfile.name; name = name.toLowerCase().replace(/\W/g, '_'); - this.exportToPc(this.prepareDeviceProfileExport(deviceProfile), name); + this.exportToPc(this.prepareProfileExport(deviceProfile), name); }, (e) => { this.handleExportError(e, 'device-profile.export-failed-error'); @@ -460,6 +463,37 @@ export class ImportExportService { ); } + public exportTenantProfile(tenantProfileId: string) { + this.tenantProfileService.getTenantProfile(tenantProfileId).subscribe( + (tenantProfile) => { + let name = tenantProfile.name; + name = name.toLowerCase().replace(/\W/g, '_'); + this.exportToPc(this.prepareProfileExport(tenantProfile), name); + }, + (e) => { + this.handleExportError(e, 'tenant-profile.export-failed-error'); + } + ); + } + + public importTenantProfile(): Observable { + return this.openImportDialog('tenant-profile.import', 'tenant-profile.tenant-profile-file').pipe( + mergeMap((tenantProfile: TenantProfile) => { + if (!this.validateImportedTenantProfile(tenantProfile)) { + this.store.dispatch(new ActionNotificationShow( + {message: this.translate.instant('tenant-profile.invalid-tenant-profile-file-error'), + type: 'error'})); + throw new Error('Invalid tenant profile file'); + } else { + return this.tenantProfileService.saveTenantProfile(tenantProfile); + } + }), + catchError(() => { + return of(null); + }) + ); + } + public exportJSZip(data: object, filename: string) { import('jszip').then((JSZip) => { const jsZip = new JSZip.default(); @@ -517,6 +551,13 @@ export class ImportExportService { return true; } + private validateImportedTenantProfile(tenantProfile: TenantProfile): boolean { + return isDefined(tenantProfile.name) + && isDefined(tenantProfile.profileData) + && isDefined(tenantProfile.isolatedTbCore) + && isDefined(tenantProfile.isolatedTbRuleEngine); + } + private sumObject(obj1: any, obj2: any): any { Object.keys(obj2).map((key) => { if (isObject(obj2[key])) { @@ -798,10 +839,10 @@ export class ImportExportService { return dashboard; } - private prepareDeviceProfileExport(deviceProfile: DeviceProfile): DeviceProfile { - deviceProfile = this.prepareExport(deviceProfile); - deviceProfile.default = false; - return deviceProfile; + private prepareProfileExport(profile: T): T { + profile = this.prepareExport(profile); + profile.default = false; + return profile; } private prepareExport(data: any): any { diff --git a/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts index 996058e753..6c280c096b 100644 --- a/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts @@ -21,7 +21,8 @@ import { checkBoxCell, DateEntityTableColumn, EntityTableColumn, - EntityTableConfig + EntityTableConfig, + HeaderActionDescriptor } from '@home/models/entity/entities-table-config.models'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; @@ -31,6 +32,7 @@ import { TenantProfileService } from '@core/http/tenant-profile.service'; import { TenantProfileComponent } from '../../components/profile/tenant-profile.component'; import { TenantProfileTabsComponent } from './tenant-profile-tabs.component'; import { DialogService } from '@core/services/dialog.service'; +import { ImportExportService } from '@home/components/import-export/import-export.service'; @Injectable() export class TenantProfilesTableConfigResolver implements Resolve> { @@ -38,6 +40,7 @@ export class TenantProfilesTableConfigResolver implements Resolve = new EntityTableConfig(); constructor(private tenantProfileService: TenantProfileService, + private importExport: ImportExportService, private translate: TranslateService, private datePipe: DatePipe, private dialogService: DialogService) { @@ -59,6 +62,12 @@ export class TenantProfilesTableConfigResolver implements Resolve true, + onAction: ($event, entity) => this.exportTenantProfile($event, entity) + }, { name: this.translate.instant('tenant-profile.set-default'), icon: 'flag', @@ -80,6 +89,7 @@ export class TenantProfilesTableConfigResolver implements Resolve this.onTenantProfileAction(action); this.config.deleteEnabled = (tenantProfile) => tenantProfile && !tenantProfile.default; this.config.entitySelectionEnabled = (tenantProfile) => tenantProfile && !tenantProfile.default; + this.config.addActionDescriptors = this.configureAddActions(); } resolve(): EntityTableConfig { @@ -88,6 +98,25 @@ export class TenantProfilesTableConfigResolver implements Resolve { + const actions: Array = []; + actions.push( + { + name: this.translate.instant('tenant-profile.create-tenant-profile'), + icon: 'insert_drive_file', + isEnabled: () => true, + onAction: ($event) => this.config.table.addEntity($event) + }, + { + name: this.translate.instant('tenant-profile.import'), + icon: 'file_upload', + isEnabled: () => true, + onAction: ($event) => this.importTenantProfile($event) + } + ); + return actions; + } + setDefaultTenantProfile($event: Event, tenantProfile: TenantProfile) { if ($event) { $event.stopPropagation(); @@ -110,6 +139,23 @@ export class TenantProfilesTableConfigResolver implements Resolve { + if (deviceProfile) { + this.config.table.updateData(); + } + } + ); + } + + exportTenantProfile($event: Event, tenantProfile: TenantProfile) { + if ($event) { + $event.stopPropagation(); + } + this.importExport.exportTenantProfile(tenantProfile.id.id); + } + onTenantProfileAction(action: EntityAction): boolean { switch (action.action) { case 'setDefault': 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 d59ee56236..c7de3c2a1c 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2440,6 +2440,12 @@ "set-default-tenant-profile-text": "After the confirmation the tenant profile will be marked as default and will be used for new tenants with no profile specified.", "no-tenant-profiles-found": "No tenant profiles found.", "create-new-tenant-profile": "Create a new one!", + "create-tenant-profile": "Create new tenant profile", + "import": "Import tenant profile", + "export": "Export tenant profile", + "export-failed-error": "Unable to export tenant profile: {{error}}", + "tenant-profile-file": "Tenant profile file", + "invalid-tenant-profile-file-error": "Unable to import tenant profile: Invalid tenant profile data structure.", "maximum-devices": "Maximum number of devices (0 - unlimited)", "maximum-devices-required": "Maximum number of devices is required.", "maximum-devices-range": "Minimum number of devices can't be negative", From 79eb1183d92ac7d03a09876dcbc6bc75240d1026 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 27 Apr 2021 15:14:35 +0300 Subject: [PATCH 093/127] Request SNMP devices' ids with pagination --- .../transport/DefaultTransportApiService.java | 15 +++++----- .../server/dao/device/DeviceService.java | 2 +- common/queue/src/main/proto/queue.proto | 4 ++- .../service/ProtoTransportEntityService.java | 30 +++++++++++++++---- .../server/dao/device/DeviceDao.java | 3 +- .../server/dao/device/DeviceServiceImpl.java | 6 ++-- .../dao/sql/device/DeviceRepository.java | 2 +- .../server/dao/sql/device/JpaDeviceDao.java | 5 ++-- 8 files changed, 42 insertions(+), 25 deletions(-) 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 dff59985b9..826e1d84a3 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 @@ -23,7 +23,6 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; -import org.springframework.cache.CacheManager; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.common.util.JacksonUtil; @@ -46,6 +45,8 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -62,7 +63,6 @@ import org.thingsboard.server.dao.device.provision.ProvisionRequest; import org.thingsboard.server.dao.device.provision.ProvisionResponse; import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; @@ -72,9 +72,9 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequ import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetSnmpDevicesResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetResourceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; @@ -91,7 +91,6 @@ import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.state.DeviceStateService; -import java.util.List; import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -433,13 +432,15 @@ public class DefaultTransportApiService implements TransportApiService { return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setResourceResponseMsg(builder).build()); } - // TODO: request snmp devices with pagination private ListenableFuture handle(GetSnmpDevicesRequestMsg requestMsg) { - List result = deviceService.findDevicesIdsByDeviceProfileTransportType(DeviceTransportType.SNMP); + PageLink pageLink = new PageLink(requestMsg.getPageSize(), requestMsg.getPage()); + PageData result = deviceService.findDevicesIdsByDeviceProfileTransportType(DeviceTransportType.SNMP, pageLink); + GetSnmpDevicesResponseMsg responseMsg = GetSnmpDevicesResponseMsg.newBuilder() - .addAllIds(result.stream() + .addAllIds(result.getData().stream() .map(UUID::toString) .collect(Collectors.toList())) + .setHasNextPage(result.hasNext()) .build(); return Futures.immediateFuture(TransportApiResponseMsg.newBuilder() diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index a6a8e6d719..8e111fa33f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -95,7 +95,7 @@ public interface DeviceService { Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile); - List findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType); + PageData findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink); Device assignDeviceToEdge(TenantId tenantId, DeviceId deviceId, EdgeId edgeId); diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 3800a5d082..340f9c1b05 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -268,11 +268,13 @@ message GetDeviceCredentialsResponseMsg { } message GetSnmpDevicesRequestMsg { - + int32 page = 1; + int32 pageSize = 2; } message GetSnmpDevicesResponseMsg { repeated string ids = 1; + bool hasNextPage = 2; } message EntityUpdateMsg { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java index 1f639daa2a..957d610f91 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -81,12 +82,29 @@ public class ProtoTransportEntityService { } public List getAllSnmpDevicesIds() { - TransportProtos.GetSnmpDevicesResponseMsg devicesIdsResponse = transportService.getSnmpDevicesIds( - TransportProtos.GetSnmpDevicesRequestMsg.getDefaultInstance() - ); + List result = new ArrayList<>(); + + int page = 0; + int pageSize = 512; + boolean hasNextPage = true; + + while (hasNextPage) { + TransportProtos.GetSnmpDevicesResponseMsg responseMsg = requestSnmpDevicesIds(page, pageSize); + result.addAll(responseMsg.getIdsList().stream() + .map(UUID::fromString) + .collect(Collectors.toList())); + hasNextPage = responseMsg.getHasNextPage(); + page++; + } + + return result; + } - return devicesIdsResponse.getIdsList().stream() - .map(UUID::fromString) - .collect(Collectors.toList()); + private TransportProtos.GetSnmpDevicesResponseMsg requestSnmpDevicesIds(int page, int pageSize) { + TransportProtos.GetSnmpDevicesRequestMsg requestMsg = TransportProtos.GetSnmpDevicesRequestMsg.newBuilder() + .setPage(page) + .setPageSize(pageSize) + .build(); + return transportService.getSnmpDevicesIds(requestMsg); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index d325be4ddb..a714d5ec53 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -23,7 +23,6 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.TenantEntityDao; @@ -220,7 +219,7 @@ public interface DeviceDao extends Dao, TenantEntityDao { */ PageData findDevicesByTenantIdAndProfileId(UUID tenantId, UUID profileId, PageLink pageLink); - List findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType); + PageData findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink); /** * Find devices by tenantId, edgeId and page link. diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index b67c44caa4..02d8bd2b15 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -42,7 +42,6 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.device.DeviceSearchQuery; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.data.CoapDeviceTransportConfiguration; @@ -62,7 +61,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -580,8 +578,8 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Override - public List findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType) { - return deviceDao.findDevicesIdsByDeviceProfileTransportType(transportType); + public PageData findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink) { + return deviceDao.findDevicesIdsByDeviceProfileTransportType(transportType, pageLink); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 33e19397a1..b6d723f812 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -215,5 +215,5 @@ public interface DeviceRepository extends PagingAndSortingRepository findIdsByDeviceProfileTransportType(@Param("transportType") DeviceTransportType transportType); + Page findIdsByDeviceProfileTransportType(@Param("transportType") DeviceTransportType transportType, Pageable pageable); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index f6b6b651e5..1291dc09e4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.device.DeviceDao; import org.thingsboard.server.dao.model.sql.DeviceEntity; @@ -119,8 +118,8 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao } @Override - public List findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType) { - return deviceRepository.findIdsByDeviceProfileTransportType(transportType); + public PageData findDevicesIdsByDeviceProfileTransportType(DeviceTransportType transportType, PageLink pageLink) { + return DaoUtil.pageToPageData(deviceRepository.findIdsByDeviceProfileTransportType(transportType, DaoUtil.toPageable(pageLink))); } @Override From b34198f30c6f852eb8fca0e9a37cd0fe2538add7 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 28 Apr 2021 16:39:13 +0300 Subject: [PATCH 094/127] refactored CoAP Server component (#4497) * refactored ce code base needed for coap-integration in pe * added anotation TbCoapServerComponent on CoapTransportService * license updated --- .../src/main/resources/thingsboard.yml | 4 +-- .../server/coapserver/CoapServerContext.java | 3 +-- .../coapserver/DefaultCoapServerService.java | 3 +-- .../server/coapserver/TbCoapDtlsSettings.java | 9 +++---- .../coapserver/TbCoapServerComponent.java | 26 +++++++++++++++++++ .../transport/coap/CoapTransportService.java | 3 ++- .../thingsboard/common/util/JacksonUtil.java | 10 +++++++ 7 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapServerComponent.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ca1654afa2..33760d174c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -621,9 +621,9 @@ transport: key_password: "${COAP_DTLS_KEY_PASSWORD:server_key_password}" # Key alias key_alias: "${COAP_DTLS_KEY_ALIAS:serveralias}" - # Skip certificate validity check for client certificates. - skip_validity_check_for_client_cert: "${COAP_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" x509: + # Skip certificate validity check for client certificates. + skip_validity_check_for_client_cert: "${TB_COAP_X509_DTLS_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" dtls_session_inactivity_timeout: "${TB_COAP_X509_DTLS_SESSION_INACTIVITY_TIMEOUT:86400000}" dtls_session_report_timeout: "${TB_COAP_X509_DTLS_SESSION_REPORT_TIMEOUT:1800000}" # Local LwM2M transport parameters diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java index 4129108ba3..0f5aae8bd8 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/CoapServerContext.java @@ -19,11 +19,10 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; @Slf4j -@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") +@TbCoapServerComponent @Component public class CoapServerContext { diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java index aebac1a86d..93f4e76551 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/DefaultCoapServerService.java @@ -23,7 +23,6 @@ import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.scandium.DTLSConnector; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; @@ -39,7 +38,7 @@ import java.util.concurrent.TimeUnit; @Slf4j @Component -@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") +@TbCoapServerComponent public class DefaultCoapServerService implements CoapServerService { @Autowired diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java index 417a78da12..d5605ee29b 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsSettings.java @@ -39,7 +39,6 @@ import java.util.Collections; import java.util.Optional; @Slf4j -@ConditionalOnExpression("'${transport.coap.enabled}'=='true'") @ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false) @Component public class TbCoapDtlsSettings { @@ -50,7 +49,7 @@ public class TbCoapDtlsSettings { @Value("${transport.coap.dtls.bind_port}") private Integer port; - @Value("${transport.coap.dtls.mode}") + @Value("${transport.coap.dtls.mode:NO_AUTH}") private String mode; @Value("${transport.coap.dtls.key_store}") @@ -65,13 +64,13 @@ public class TbCoapDtlsSettings { @Value("${transport.coap.dtls.key_alias}") private String keyAlias; - @Value("${transport.coap.dtls.skip_validity_check_for_client_cert}") + @Value("${transport.coap.dtls.x509.skip_validity_check_for_client_cert:false}") private boolean skipValidityCheckForClientCert; - @Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout}") + @Value("${transport.coap.dtls.x509.dtls_session_inactivity_timeout:86400000}") private long dtlsSessionInactivityTimeout; - @Value("${transport.coap.dtls.x509.dtls_session_report_timeout}") + @Value("${transport.coap.dtls.x509.dtls_session_report_timeout:1800000}") private long dtlsSessionReportTimeout; @Autowired diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapServerComponent.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapServerComponent.java new file mode 100644 index 0000000000..718af54ab6 --- /dev/null +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapServerComponent.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 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.coapserver; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") +public @interface TbCoapServerComponent { +} diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java index db5261dd75..a739563858 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportService.java @@ -23,6 +23,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.coapserver.CoapServerService; +import org.thingsboard.server.coapserver.TbCoapServerComponent; import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; import javax.annotation.PostConstruct; @@ -30,7 +31,7 @@ import javax.annotation.PreDestroy; import java.net.UnknownHostException; @Service("CoapTransportService") -@ConditionalOnExpression("'${service.type:null}'=='tb-transport' || ('${service.type:null}'=='monolith' && '${transport.api_enabled:true}'=='true' && '${transport.coap.enabled}'=='true')") +@TbCoapServerComponent @Slf4j public class CoapTransportService implements TbTransportService { diff --git a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java index 8d498fa853..aa250a7039 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java @@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; +import java.util.Arrays; /** * Created by Valerii Sosliuk on 5/12/2017. @@ -66,6 +67,15 @@ public class JacksonUtil { } } + public static JsonNode fromBytes(byte[] bytes) { + try { + return OBJECT_MAPPER.readTree(bytes); + } catch (IOException e) { + throw new IllegalArgumentException("The given byte[] value: " + + Arrays.toString(bytes) + " cannot be transformed to Json object", e); + } + } + public static String toString(Object value) { try { return value != null ? OBJECT_MAPPER.writeValueAsString(value) : null; From 3b74a806bc6c23d7319e561d7409fca1325a85c6 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 28 Apr 2021 16:40:45 +0300 Subject: [PATCH 095/127] RPC functionality refactoring for SNMP (#4492) * Refactor RPC functionality for SNMP * Refactor * SNMP response PDU mapping refactoring --- .../snmp/config/SnmpCommunicationConfig.java | 4 ++- ...viceRpcRequestSnmpCommunicationConfig.java | 26 ++++++++++++++ .../transport/snmp/service/PduService.java | 2 +- .../snmp/service/SnmpTransportService.java | 35 ++++++++++++------- 4 files changed, 53 insertions(+), 14 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java index e2ba29abfa..0d673a4145 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.config.impl.ClientAttributesQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.impl.SharedAttributesSettingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcRequestSnmpCommunicationConfig; import java.util.List; @@ -34,7 +35,8 @@ import java.util.List; @JsonSubTypes({ @Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"), @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), - @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING") + @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"), + @Type(value = ToDeviceRpcRequestSnmpCommunicationConfig.class, name = "TO_DEVICE_RPC_REQUEST") }) public interface SnmpCommunicationConfig { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java new file mode 100644 index 0000000000..6683500445 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcRequestSnmpCommunicationConfig.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2021 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.transport.snmp.config.impl; + +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.config.MultipleMappingsSnmpCommunicationConfig; + +public class ToDeviceRpcRequestSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig { + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST; + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java index 720b1e7ae3..2fc9057048 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java @@ -151,7 +151,7 @@ public class PduService { .collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString)); } - private void processValue(String key, DataType dataType, String value, JsonObject result) { + public void processValue(String key, DataType dataType, String value, JsonObject result) { switch (dataType) { case LONG: result.addProperty(key, Long.parseLong(value)); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 5bfee14c41..3c296cc4ae 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -183,19 +183,28 @@ public class SnmpTransportService implements TbTransportService { SnmpMethod snmpMethod = SnmpMethod.valueOf(toDeviceRpcRequestMsg.getMethodName()); JsonObject params = JsonConverter.parse(toDeviceRpcRequestMsg.getParams()).getAsJsonObject(); - String oid = Optional.ofNullable(params.get("oid")).map(JsonElement::getAsString).orElse(null); + String key = Optional.ofNullable(params.get("key")).map(JsonElement::getAsString).orElse(null); String value = Optional.ofNullable(params.get("value")).map(JsonElement::getAsString).orElse(null); - DataType dataType = Optional.ofNullable(params.get("dataType")).map(e -> DataType.valueOf(e.getAsString())).orElse(DataType.STRING); - if (oid == null || oid.isEmpty()) { - throw new IllegalArgumentException("OID in to-device RPC request is not specified"); - } if (value == null && snmpMethod == SnmpMethod.SET) { throw new IllegalArgumentException("Value must be specified for SNMP method 'SET'"); } + SnmpCommunicationConfig communicationConfig = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() + .filter(config -> config.getSpec() == SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No communication config found with RPC spec")); + SnmpMapping snmpMapping = communicationConfig.getAllMappings().stream() + .filter(mapping -> mapping.getKey().equals(key)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No SNMP mapping found in the config for specified key")); + + String oid = snmpMapping.getOid(); + DataType dataType = snmpMapping.getDataType(); + PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType); - sendRequest(sessionContext, request, new RequestInfo(toDeviceRpcRequestMsg.getRequestId(), SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST)); + RequestInfo requestInfo = new RequestInfo(toDeviceRpcRequestMsg.getRequestId(), communicationConfig.getSpec(), communicationConfig.getAllMappings()); + sendRequest(sessionContext, request, requestInfo); } @@ -238,7 +247,12 @@ public class SnmpTransportService implements TbTransportService { responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdu, requestInfo) -> { JsonObject responseData = new JsonObject(); pduService.processPdu(pdu).forEach((oid, value) -> { - responseData.addProperty(oid.toDottedString(), value); + requestInfo.getResponseMappings().stream() + .filter(snmpMapping -> snmpMapping.getOid().equals(oid.toDottedString())) + .findFirst() + .ifPresent(snmpMapping -> { + pduService.processValue(snmpMapping.getKey(), snmpMapping.getDataType(), value, responseData); + }); }); return responseData; }); @@ -314,13 +328,10 @@ public class SnmpTransportService implements TbTransportService { private SnmpCommunicationSpec communicationSpec; private List responseMappings; - public RequestInfo(Integer requestId, SnmpCommunicationSpec communicationSpec) { + public RequestInfo(Integer requestId, SnmpCommunicationSpec communicationSpec, List responseMappings) { this.requestId = requestId; this.communicationSpec = communicationSpec; - } - - public RequestInfo(SnmpCommunicationSpec communicationSpec) { - this.communicationSpec = communicationSpec; + this.responseMappings = responseMappings; } public RequestInfo(SnmpCommunicationSpec communicationSpec, List responseMappings) { From 1de97ad0e1586d3917755cc257ff0bbf8d5e43e7 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 29 Apr 2021 11:12:53 +0300 Subject: [PATCH 096/127] Refactor SNMP devices' sessions establishing --- .../transport/snmp/SnmpTransportContext.java | 34 +++++++++++-------- .../service/ProtoTransportEntityService.java | 21 +----------- 2 files changed, 20 insertions(+), 35 deletions(-) diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 68d4dd3933..0efde4d893 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -55,7 +55,6 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedDeque; -import java.util.stream.Collectors; @TbSnmpTransportComponent @Component @@ -72,25 +71,30 @@ public class SnmpTransportContext extends TransportContext { private final SnmpAuthService snmpAuthService; private final Map sessions = new ConcurrentHashMap<>(); - private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); + private final Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); @AfterStartUp(order = 2) - public void initDevicesSessions() { + public void fetchDevicesAndEstablishSessions() { log.info("Initializing SNMP devices sessions"); - allSnmpDevicesIds = protoEntityService.getAllSnmpDevicesIds().stream() - .map(DeviceId::new) - .collect(Collectors.toList()); - log.trace("Found all SNMP devices ids: {}", allSnmpDevicesIds); - List managedDevicesIds = allSnmpDevicesIds.stream() - .filter(deviceId -> balancingService.isManagedByCurrentTransport(deviceId.getId())) - .collect(Collectors.toList()); - log.info("SNMP devices managed by current SNMP transport: {}", managedDevicesIds); + int batchIndex = 0; + int batchSize = 512; + boolean nextBatchExists = true; - managedDevicesIds.stream() - .map(protoEntityService::getDeviceById) - .collect(Collectors.toList()) - .forEach(this::establishDeviceSession); + while (nextBatchExists) { + TransportProtos.GetSnmpDevicesResponseMsg snmpDevicesResponse = protoEntityService.getSnmpDevicesIds(batchIndex, batchSize); + snmpDevicesResponse.getIdsList().stream() + .map(id -> new DeviceId(UUID.fromString(id))) + .peek(allSnmpDevicesIds::add) + .filter(deviceId -> balancingService.isManagedByCurrentTransport(deviceId.getId())) + .map(protoEntityService::getDeviceById) + .forEach(device -> getExecutor().execute(() -> establishDeviceSession(device))); + + nextBatchExists = snmpDevicesResponse.getHasNextPage(); + batchIndex++; + } + + log.debug("Found all SNMP devices ids: {}", allSnmpDevicesIds); } private void establishDeviceSession(Device device) { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java index 957d610f91..777650f779 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/ProtoTransportEntityService.java @@ -81,26 +81,7 @@ public class ProtoTransportEntityService { .orElseThrow(() -> new IllegalArgumentException("Device credentials not found")); } - public List getAllSnmpDevicesIds() { - List result = new ArrayList<>(); - - int page = 0; - int pageSize = 512; - boolean hasNextPage = true; - - while (hasNextPage) { - TransportProtos.GetSnmpDevicesResponseMsg responseMsg = requestSnmpDevicesIds(page, pageSize); - result.addAll(responseMsg.getIdsList().stream() - .map(UUID::fromString) - .collect(Collectors.toList())); - hasNextPage = responseMsg.getHasNextPage(); - page++; - } - - return result; - } - - private TransportProtos.GetSnmpDevicesResponseMsg requestSnmpDevicesIds(int page, int pageSize) { + public TransportProtos.GetSnmpDevicesResponseMsg getSnmpDevicesIds(int page, int pageSize) { TransportProtos.GetSnmpDevicesRequestMsg requestMsg = TransportProtos.GetSnmpDevicesRequestMsg.newBuilder() .setPage(page) .setPageSize(pageSize) From b4b5d062b06a7d617cb1ace2c4480b2ed9972f84 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Apr 2021 19:00:17 +0300 Subject: [PATCH 097/127] fixed alarm query (search without created time column) --- .../query/DefaultAlarmQueryRepository.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 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 5173b83fb8..a0ce183b97 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 @@ -17,12 +17,8 @@ package org.thingsboard.server.dao.sql.query; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; @@ -40,8 +36,6 @@ import org.thingsboard.server.common.data.query.EntityKey; import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.dao.model.ModelConstants; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -134,6 +128,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { StringBuilder fromPart = new StringBuilder(" from alarm a "); StringBuilder wherePart = new StringBuilder(" where "); StringBuilder sortPart = new StringBuilder(" order by "); + StringBuilder joinPart = new StringBuilder(); boolean addAnd = false; if (pageLink.isSearchPropagatedAlarms()) { selectPart.append(" CASE WHEN r.from_id IS NULL THEN a.originator_id ELSE r.from_id END as entity_id "); @@ -156,23 +151,23 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { wherePart.append(" a.originator_id in (:entity_ids)"); } } else { - fromPart.append(" inner join (select * from (VALUES"); + joinPart.append(" inner join (select * from (VALUES"); int entityIdIdx = 0; int lastEntityIdIdx = orderedEntityIds.size() - 1; for (EntityId entityId : orderedEntityIds) { - fromPart.append("(uuid('").append(entityId.getId().toString()).append("'), ").append(entityIdIdx).append(")"); + joinPart.append("(uuid('").append(entityId.getId().toString()).append("'), ").append(entityIdIdx).append(")"); if (entityIdIdx != lastEntityIdIdx) { - fromPart.append(","); + joinPart.append(","); } else { - fromPart.append(")"); + joinPart.append(")"); } entityIdIdx++; } - fromPart.append(" as e(id, priority)) e "); + joinPart.append(" as e(id, priority)) e "); if (pageLink.isSearchPropagatedAlarms()) { - fromPart.append("on (r.from_id IS NULL and a.originator_id = e.id) or (r.from_id IS NOT NULL and r.from_id = e.id)"); + joinPart.append("on (r.from_id IS NULL and a.originator_id = e.id) or (r.from_id IS NOT NULL and r.from_id = e.id)"); } else { - fromPart.append("on a.originator_id = e.id"); + joinPart.append("on a.originator_id = e.id"); } sortPart.append("e.priority"); } @@ -226,9 +221,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { } String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch()); - String mainQuery = selectPart.toString() + fromPart.toString() + wherePart.toString(); + String mainQuery; if (!textSearchQuery.isEmpty()) { - mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery); + mainQuery = selectPart.toString() + fromPart.toString() + wherePart.toString(); + mainQuery = String.format("select * from (%s) a %s WHERE %s", mainQuery, joinPart, textSearchQuery); + } else { + mainQuery = selectPart.toString() + fromPart.toString() + joinPart.toString() + wherePart.toString(); } String countQuery = String.format("select count(*) from (%s) result", mainQuery); long queryTs = System.currentTimeMillis(); From 39f07757664f450f7ef04152ecdc9398b0353828 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko <79898499+smatvienko-tb@users.noreply.github.com> Date: Thu, 29 Apr 2021 14:15:50 +0300 Subject: [PATCH 098/127] Thread pool naming CE (#4511) * added ThingsBoardExecutors and ThingsBoardForkJoinWorkerThreadFactory to define the name for the pool thread * thread poll named for the AbstractListeningExecutor * thread poll named for the RestClient. ThingsBoard util dependency added * most of thread polls named CE * thread poll name added for telemetry-web-socket-ping * executors: added custom names for executors (Queue, RuleEngine). Add topic name to the Thread name (useful for JMX and thread dump) * fixed licence header for a new classes --- .../actors/service/DefaultActorService.java | 3 +- .../DefaultTbApiUsageStateService.java | 3 +- .../DefaultTbRuleEngineConsumerService.java | 4 +- .../AbstractNashornJsInvokeService.java | 3 +- .../DefaultTbLocalSubscriptionService.java | 5 +- .../DefaultTelemetryWebSocketService.java | 6 ++- .../transport/TbCoreTransportApiService.java | 3 +- .../common/DefaultTbQueueRequestTemplate.java | 3 +- .../DefaultTbQueueResponseTemplate.java | 5 +- .../common/transport/TransportContext.java | 3 +- .../service/DefaultTransportService.java | 3 +- .../util/AbstractListeningExecutor.java | 2 +- .../common/util/ThingsBoardExecutors.java | 50 +++++++++++++++++++ ...hingsBoardForkJoinWorkerThreadFactory.java | 41 +++++++++++++++ .../util/AbstractBufferedRateExecutor.java | 3 +- rest-client/pom.xml | 4 ++ .../thingsboard/rest/client/RestClient.java | 3 +- 17 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java create mode 100644 common/util/src/main/java/org/thingsboard/common/util/ThingsBoardForkJoinWorkerThreadFactory.java diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index f3a07eb727..7a28c205bc 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -22,6 +22,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.DefaultTbActorSystem; @@ -108,7 +109,7 @@ public class DefaultActorService extends TbApplicationEventListener tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration)); consumerStats.put(configuration.getName(), new TbRuleEngineConsumerStats(configuration.getName(), statsFactory)); } - submitExecutor = Executors.newSingleThreadExecutor(); + submitExecutor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("tb-rule-engine-consumer-service-submit-executor")); } @PreDestroy @@ -160,6 +161,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< private void launchConsumer(TbQueueConsumer> consumer, TbRuleEngineQueueConfiguration configuration, TbRuleEngineConsumerStats stats) { consumersExecutor.execute(() -> { + Thread.currentThread().setName("" + Thread.currentThread().getName() + "-" + configuration.getName()); while (!stopped) { try { List> msgs = consumer.poll(pollDuration); diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index f581145907..9985ac60a2 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -25,6 +25,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; @@ -93,7 +94,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer super.init(maxRequestsTimeout); if (useJsSandbox()) { sandbox = NashornSandboxes.create(); - monitorExecutorService = Executors.newWorkStealingPool(getMonitorThreadPoolSize()); + monitorExecutorService = ThingsBoardExecutors.newWorkStealingPool(getMonitorThreadPoolSize(), "nashorn-js-monitor"); sandbox.setExecutor(monitorExecutorService); sandbox.setMaxCPUTime(getMaxCpuTime()); sandbox.allowNoBraces(false); 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 710d613b37..0f1765f316 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 @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.event.ClusterTopologyChangeEvent; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; @@ -62,7 +63,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer private SubscriptionManagerService subscriptionManagerService; private ExecutorService subscriptionUpdateExecutor; - + private TbApplicationEventListener partitionChangeListener = new TbApplicationEventListener<>() { @Override protected void onTbApplicationEvent(PartitionChangeEvent event) { @@ -93,7 +94,7 @@ public class DefaultTbLocalSubscriptionService implements TbLocalSubscriptionSer @PostConstruct public void initExecutor() { - subscriptionUpdateExecutor = Executors.newWorkStealingPool(20); + subscriptionUpdateExecutor = ThingsBoardExecutors.newWorkStealingPool(20, getClass()); } @PreDestroy diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java index f8f4afd823..73eed820ab 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java @@ -28,6 +28,8 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.springframework.web.socket.CloseStatus; +import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; @@ -157,9 +159,9 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi @PostConstruct public void initExecutor() { serviceId = serviceInfoProvider.getServiceId(); - executor = Executors.newWorkStealingPool(50); + executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass()); - pingExecutor = Executors.newSingleThreadScheduledExecutor(); + pingExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("telemetry-web-socket-ping")); pingExecutor.scheduleWithFixedDelay(this::sendPing, 10000, 10000, TimeUnit.MILLISECONDS); } diff --git a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java index e9048d2d1c..dc6255cbc4 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/TbCoreTransportApiService.java @@ -21,6 +21,7 @@ import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.stats.MessagesStats; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsType; @@ -70,7 +71,7 @@ public class TbCoreTransportApiService { @PostConstruct public void init() { - this.transportCallbackExecutor = Executors.newWorkStealingPool(maxCallbackThreads); + this.transportCallbackExecutor = ThingsBoardExecutors.newWorkStealingPool(maxCallbackThreads, getClass()); TbQueueProducer> producer = tbCoreQueueFactory.createTransportApiResponseProducer(); TbQueueConsumer> consumer = tbCoreQueueFactory.createTransportApiRequestConsumer(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java index 9562964fda..b171f2d8d8 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplate.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.Builder; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueCallback; @@ -77,7 +78,7 @@ public class DefaultTbQueueRequestTemplate(queueLimit); this.dispatcherExecutor = Executors.newFixedThreadPool(dispatcherThreads, ThingsBoardThreadFactory.forName("nosql-dispatcher")); - this.callbackExecutor = Executors.newWorkStealingPool(callbackThreads); + this.callbackExecutor = ThingsBoardExecutors.newWorkStealingPool(callbackThreads, getClass()); this.timeoutExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nosql-timeout")); this.perTenantLimitsEnabled = perTenantLimitsEnabled; this.perTenantLimitsConfiguration = perTenantLimitsConfiguration; diff --git a/rest-client/pom.xml b/rest-client/pom.xml index e45db432d8..7272861a3b 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -43,6 +43,10 @@ org.springframework spring-web + + org.thingsboard.common + util + diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index e51117c080..433e2f8d96 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -31,6 +31,7 @@ import org.springframework.http.client.support.HttpRequestWrapper; import org.springframework.util.StringUtils; import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.RestTemplate; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.rest.client.utils.RestJsonConverter; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.ClaimRequest; @@ -144,7 +145,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { private String token; private String refreshToken; private final ObjectMapper objectMapper = new ObjectMapper(); - private ExecutorService service = Executors.newWorkStealingPool(10); + private ExecutorService service = ThingsBoardExecutors.newWorkStealingPool(10, getClass()); protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; From 42fb44ff31efaf106a6981537805873c26aa9ffb Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 27 Apr 2021 13:07:21 +0300 Subject: [PATCH 099/127] concurrency test fix TbMsgPackProcessingContextTest (readyLatch). any logs commented to not affect concurrency with sync logger. refactored --- .../queue/TbMsgPackProcessingContextTest.java | 71 ++++++++++++++----- 1 file changed, 53 insertions(+), 18 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java b/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java index 6c4e4a9426..1659431540 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java +++ b/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java @@ -16,10 +16,10 @@ package org.thingsboard.server.service.queue; import lombok.extern.slf4j.Slf4j; +import org.junit.After; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -28,39 +28,74 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrateg import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @Slf4j @RunWith(MockitoJUnitRunner.class) public class TbMsgPackProcessingContextTest { + public static final int TIMEOUT = 10; + ExecutorService executorService; + + @After + public void tearDown() { + if (executorService != null) { + executorService.shutdownNow(); + } + } + @Test public void testHighConcurrencyCase() throws InterruptedException { - TbRuleEngineSubmitStrategy strategyMock = mock(TbRuleEngineSubmitStrategy.class); + //log.warn("preparing the test..."); int msgCount = 1000; int parallelCount = 5; - ExecutorService executorService = Executors.newFixedThreadPool(parallelCount); - try { - ConcurrentMap> messages = new ConcurrentHashMap<>(); - for (int i = 0; i < msgCount; i++) { - messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null)); - } - when(strategyMock.getPendingMap()).thenReturn(messages); - TbMsgPackProcessingContext context = new TbMsgPackProcessingContext("Main", strategyMock); - for (UUID uuid : messages.keySet()) { - for (int i = 0; i < parallelCount; i++) { - executorService.submit(() -> context.onSuccess(uuid)); - } + executorService = Executors.newFixedThreadPool(parallelCount); + + ConcurrentMap> messages = new ConcurrentHashMap<>(msgCount); + for (int i = 0; i < msgCount; i++) { + messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null)); + } + TbRuleEngineSubmitStrategy strategyMock = mock(TbRuleEngineSubmitStrategy.class); + when(strategyMock.getPendingMap()).thenReturn(messages); + + TbMsgPackProcessingContext context = new TbMsgPackProcessingContext("Main", strategyMock); + for (UUID uuid : messages.keySet()) { + final CountDownLatch readyLatch = new CountDownLatch(parallelCount); + final CountDownLatch startLatch = new CountDownLatch(1); + final CountDownLatch finishLatch = new CountDownLatch(parallelCount); + for (int i = 0; i < parallelCount; i++) { + //final String taskName = "" + uuid + " " + i; + executorService.submit(() -> { + //log.warn("ready {}", taskName); + readyLatch.countDown(); + try { + startLatch.await(); + } catch (InterruptedException e) { + Assert.fail("failed to await"); + } + //log.warn("go {}", taskName); + + context.onSuccess(uuid); + + finishLatch.countDown(); + }); } - Assert.assertTrue(context.await(10, TimeUnit.SECONDS)); - Mockito.verify(strategyMock, Mockito.times(msgCount)).onSuccess(Mockito.any(UUID.class)); - } finally { - executorService.shutdownNow(); + assertTrue(readyLatch.await(TIMEOUT, TimeUnit.SECONDS)); + Thread.yield(); + startLatch.countDown(); //run all-at-once submitted tasks + assertTrue(finishLatch.await(TIMEOUT, TimeUnit.SECONDS)); } + assertTrue(context.await(TIMEOUT, TimeUnit.SECONDS)); + verify(strategyMock, times(msgCount)).onSuccess(any(UUID.class)); } } From 5ad113ad1a6476e4b291c3b31b8903d8a23a8fd7 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 23 Apr 2021 14:29:00 +0300 Subject: [PATCH 100/127] queue consumer: not going to sleep after pull if time left less then 1 millisecond. topic added to logs --- .../common/AbstractTbQueueConsumerTemplate.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java index 4481e52461..fc3ddf22ff 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java @@ -38,6 +38,7 @@ import static java.util.Collections.emptyList; @Slf4j public abstract class AbstractTbQueueConsumerTemplate implements TbQueueConsumer { + public static final long ONE_MILLISECOND_IN_NANOS = TimeUnit.MILLISECONDS.toNanos(1); private volatile boolean subscribed; protected volatile boolean stopped = false; protected volatile Set partitions; @@ -83,7 +84,7 @@ public abstract class AbstractTbQueueConsumerTemplate i } if (consumerLock.isLocked()) { - log.error("poll. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock", new RuntimeException("stacktrace")); + log.error("poll. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock topic " + topic, new RuntimeException("stacktrace")); } consumerLock.lock(); @@ -131,9 +132,12 @@ public abstract class AbstractTbQueueConsumerTemplate i List sleepAndReturnEmpty(final long startNanos, final long durationInMillis) { long durationNanos = TimeUnit.MILLISECONDS.toNanos(durationInMillis); long spentNanos = System.nanoTime() - startNanos; - if (spentNanos < durationNanos) { + long nanosLeft = durationNanos - spentNanos; + if (nanosLeft >= ONE_MILLISECOND_IN_NANOS) { try { - Thread.sleep(Math.max(TimeUnit.NANOSECONDS.toMillis(durationNanos - spentNanos), 1)); + long sleepMs = TimeUnit.NANOSECONDS.toMillis(nanosLeft); + log.trace("Going to sleep after poll: topic {} for {}ms", topic, sleepMs); + Thread.sleep(sleepMs); } catch (InterruptedException e) { if (!stopped) { log.error("Failed to wait", e); @@ -146,7 +150,7 @@ public abstract class AbstractTbQueueConsumerTemplate i @Override public void commit() { if (consumerLock.isLocked()) { - log.error("commit. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock", new RuntimeException("stacktrace")); + log.error("commit. consumerLock is locked. will wait with no timeout. it looks like a race conditions or deadlock topic " + topic, new RuntimeException("stacktrace")); } consumerLock.lock(); try { From c25964d7472b2e3dd870e24198ddab2c1daf5c8f Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 28 Apr 2021 15:13:26 +0300 Subject: [PATCH 101/127] Added prometheus-grafana monitoring to Docker scripts --- docker/.env | 3 + docker/README.md | 10 +++ docker/compose-utils.sh | 12 +++ docker/docker-compose.prometheus-grafana.yml | 47 +++++++++++ docker/docker-remove-services.sh | 4 +- docker/docker-start-services.sh | 4 +- docker/docker-stop-services.sh | 4 +- docker/monitoring/grafana/config.monitoring | 2 + .../provisioning/dashboards/dashboard.yml | 27 ++++++ .../provisioning/datasources/datasource.yml | 66 +++++++++++++++ docker/monitoring/prometheus/prometheus.yml | 83 +++++++++++++++++++ docker/tb-coap-transport.env | 6 ++ docker/tb-http-transport.env | 3 + docker/tb-mqtt-transport.env | 6 ++ docker/tb-node.env | 3 + 15 files changed, 277 insertions(+), 3 deletions(-) create mode 100644 docker/docker-compose.prometheus-grafana.yml create mode 100644 docker/monitoring/grafana/config.monitoring create mode 100644 docker/monitoring/grafana/provisioning/dashboards/dashboard.yml create mode 100644 docker/monitoring/grafana/provisioning/datasources/datasource.yml create mode 100644 docker/monitoring/prometheus/prometheus.yml diff --git a/docker/.env b/docker/.env index e63e1dbf8d..11e44fdde8 100644 --- a/docker/.env +++ b/docker/.env @@ -19,3 +19,6 @@ TB_VERSION=latest DATABASE=postgres LOAD_BALANCER_NAME=haproxy-certbot + +# If enabled Prometheus and Grafana containers are deployed along with other containers +MONITORING_ENABLED=false \ No newline at end of file diff --git a/docker/README.md b/docker/README.md index 9664edc48e..01f89ebb6c 100644 --- a/docker/README.md +++ b/docker/README.md @@ -100,3 +100,13 @@ $ ./docker-start-services.sh Where: - `FROM_VERSION` - from which version upgrade should be started. See [Upgrade Instructions](https://thingsboard.io/docs/user-guide/install/upgrade-instructions) for valid `fromVersion` values. + + +## Monitoring + +If you want to enable monitoring with Prometheus and Grafana you need to set MONITORING_ENABLED environment variable to true. +After this Prometheus and Grafana containers will be deployed. You can reach Prometheus at `http://localhost:9090` and Grafana at `http://localhost:3000` (default login is `admin` and password `foobar`). +To change Grafana password you need to update `GF_SECURITY_ADMIN_PASSWORD` environment variable at `./monitoring/grafana/config.monitoring` file. +Dashboards are loaded from `./monitoring/grafana/provisioning/dashboards` directory. + +If you want to add new monitoring jobs for Prometheus update `./monitoring/prometheus/prometheus.yml` file. \ No newline at end of file diff --git a/docker/compose-utils.sh b/docker/compose-utils.sh index 6afc5ea2c1..db10fcd046 100755 --- a/docker/compose-utils.sh +++ b/docker/compose-utils.sh @@ -61,6 +61,18 @@ function additionalComposeQueueArgs() { echo $ADDITIONAL_COMPOSE_QUEUE_ARGS } +function additionalComposeMonitoringArgs() { + source .env + + if [ "$MONITORING_ENABLED" = true ] + then + ADDITIONAL_COMPOSE_MONITORING_ARGS="-f docker-compose.prometheus-grafana.yml" + echo $ADDITIONAL_COMPOSE_MONITORING_ARGS + else + echo "" + fi +} + function additionalStartupServices() { source .env ADDITIONAL_STARTUP_SERVICES="" diff --git a/docker/docker-compose.prometheus-grafana.yml b/docker/docker-compose.prometheus-grafana.yml new file mode 100644 index 0000000000..4fd53c68ec --- /dev/null +++ b/docker/docker-compose.prometheus-grafana.yml @@ -0,0 +1,47 @@ +# +# Copyright © 2016-2021 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. +# + +version: '2.2' + +volumes: + prometheus_data: {} + grafana_data: {} + +services: + + prometheus: + image: prom/prometheus:v2.1.0 + volumes: + - ./monitoring/prometheus/:/etc/prometheus/ + - prometheus_data:/prometheus + command: + - '--config.file=/etc/prometheus/prometheus.yml' + ports: + - 9090:9090 + restart: always + grafana: + image: grafana/grafana + user: "472" + depends_on: + - prometheus + ports: + - 3000:3000 + volumes: + - grafana_data:/var/lib/grafana + - ./monitoring/grafana/provisioning/:/etc/grafana/provisioning/ + env_file: + - monitoring/grafana/config.monitoring + restart: always \ No newline at end of file diff --git a/docker/docker-remove-services.sh b/docker/docker-remove-services.sh index b2937e6c37..cd7a648b80 100755 --- a/docker/docker-remove-services.sh +++ b/docker/docker-remove-services.sh @@ -23,4 +23,6 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS down -v +ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? + +docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS down -v diff --git a/docker/docker-start-services.sh b/docker/docker-start-services.sh index a3cb339652..75f98897ee 100755 --- a/docker/docker-start-services.sh +++ b/docker/docker-start-services.sh @@ -23,4 +23,6 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS up -d +ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? + +docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS up -d diff --git a/docker/docker-stop-services.sh b/docker/docker-stop-services.sh index d6751a1836..95f7f696f5 100755 --- a/docker/docker-stop-services.sh +++ b/docker/docker-stop-services.sh @@ -23,4 +23,6 @@ ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? -docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS stop +ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? + +docker-compose -f docker-compose.yml $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS stop diff --git a/docker/monitoring/grafana/config.monitoring b/docker/monitoring/grafana/config.monitoring new file mode 100644 index 0000000000..f12466bb05 --- /dev/null +++ b/docker/monitoring/grafana/config.monitoring @@ -0,0 +1,2 @@ +GF_SECURITY_ADMIN_PASSWORD=foobar +GF_USERS_ALLOW_SIGN_UP=false diff --git a/docker/monitoring/grafana/provisioning/dashboards/dashboard.yml b/docker/monitoring/grafana/provisioning/dashboards/dashboard.yml new file mode 100644 index 0000000000..17a0d20d15 --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/dashboard.yml @@ -0,0 +1,27 @@ +# +# Copyright © 2016-2021 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. +# + +apiVersion: 1 + +providers: +- name: 'Prometheus' + orgId: 1 + folder: '' + type: file + disableDeletion: false + editable: true + options: + path: /etc/grafana/provisioning/dashboards diff --git a/docker/monitoring/grafana/provisioning/datasources/datasource.yml b/docker/monitoring/grafana/provisioning/datasources/datasource.yml new file mode 100644 index 0000000000..c57361a4fa --- /dev/null +++ b/docker/monitoring/grafana/provisioning/datasources/datasource.yml @@ -0,0 +1,66 @@ +# +# Copyright © 2016-2021 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. +# + +# config file version +apiVersion: 1 + +# list of datasources that should be deleted from the database +deleteDatasources: + - name: Prometheus + orgId: 1 + +# list of datasources to insert/update depending +# whats available in the database +datasources: + # name of the datasource. Required +- name: Prometheus + # datasource type. Required + type: prometheus + # access mode. direct or proxy. Required + access: proxy + # org id. will default to orgId 1 if not specified + orgId: 1 + # url + url: http://prometheus:9090 + # database password, if used + password: + # database user, if used + user: + # database name, if used + database: + # enable/disable basic auth + basicAuth: false + # basic auth username, if used + basicAuthUser: + # basic auth password, if used + basicAuthPassword: + # enable/disable with credentials headers + withCredentials: + # mark as default datasource. Max one per org + isDefault: true + # fields that will be converted to json and stored in json_data + jsonData: + graphiteVersion: "1.1" + tlsAuth: false + tlsAuthWithCACert: false + # json object of data that will be encrypted. + secureJsonData: + tlsCACert: "..." + tlsClientCert: "..." + tlsClientKey: "..." + version: 1 + # allow users to edit datasources from the UI. + editable: true diff --git a/docker/monitoring/prometheus/prometheus.yml b/docker/monitoring/prometheus/prometheus.yml new file mode 100644 index 0000000000..a3b4c54e9c --- /dev/null +++ b/docker/monitoring/prometheus/prometheus.yml @@ -0,0 +1,83 @@ +# +# Copyright © 2016-2021 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. +# + +# my global config +global: + scrape_interval: 15s # By default, scrape targets every 15 seconds. + evaluation_interval: 15s # By default, scrape targets every 15 seconds. + # scrape_timeout is set to the global default (10s). + + # Attach these labels to any time series or alerts when communicating with + # external systems (federation, remote storage, Alertmanager). + external_labels: + monitor: 'thingsboard' + + +# A scrape configuration containing exactly one endpoint to scrape: +# Here it's Prometheus itself. +scrape_configs: + # The job name is added as a label `job=` to any timeseries scraped from this config. + + - job_name: 'prometheus' + scrape_interval: 5s + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'tb-core1' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-core1:8080' ] + + - job_name: 'tb-core2' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-core2:8080' ] + + - job_name: 'tb-rule-engine1' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-rule-engine1:8080' ] + + - job_name: 'tb-rule-engine2' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-rule-engine2:8080' ] + + - job_name: 'tb-mqtt-transport1' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-mqtt-transport1:8081' ] + + - job_name: 'tb-mqtt-transport2' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-mqtt-transport2:8081' ] + + - job_name: 'tb-http-transport1' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-http-transport1:8081' ] + - + - job_name: 'tb-http-transport2' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-http-transport2:8081' ] + + - job_name: 'tb-coap-transport' + metrics_path: /actuator/prometheus + static_configs: + - targets: [ 'tb-coap-transport:8081' ] + diff --git a/docker/tb-coap-transport.env b/docker/tb-coap-transport.env index 406367a192..77ec90c2db 100644 --- a/docker/tb-coap-transport.env +++ b/docker/tb-coap-transport.env @@ -4,3 +4,9 @@ ZOOKEEPER_URL=zookeeper:2181 COAP_BIND_ADDRESS=0.0.0.0 COAP_BIND_PORT=5683 COAP_TIMEOUT=10000 + +METRICS_ENABLED=true +METRICS_ENDPOINTS_EXPOSE=prometheus +WEB_APPLICATION_ENABLE=true +WEB_APPLICATION_TYPE=servlet +HTTP_BIND_PORT=8081 \ No newline at end of file diff --git a/docker/tb-http-transport.env b/docker/tb-http-transport.env index 41fb76453d..ab49d9b326 100644 --- a/docker/tb-http-transport.env +++ b/docker/tb-http-transport.env @@ -4,3 +4,6 @@ ZOOKEEPER_URL=zookeeper:2181 HTTP_BIND_ADDRESS=0.0.0.0 HTTP_BIND_PORT=8081 HTTP_REQUEST_TIMEOUT=60000 + +METRICS_ENABLED=true +METRICS_ENDPOINTS_EXPOSE=prometheus \ No newline at end of file diff --git a/docker/tb-mqtt-transport.env b/docker/tb-mqtt-transport.env index 04f6ed8e98..a0927d9a2f 100644 --- a/docker/tb-mqtt-transport.env +++ b/docker/tb-mqtt-transport.env @@ -4,3 +4,9 @@ ZOOKEEPER_URL=zookeeper:2181 MQTT_BIND_ADDRESS=0.0.0.0 MQTT_BIND_PORT=1883 MQTT_TIMEOUT=10000 + +METRICS_ENABLED=true +METRICS_ENDPOINTS_EXPOSE=prometheus +WEB_APPLICATION_ENABLE=true +WEB_APPLICATION_TYPE=servlet +HTTP_BIND_PORT=8081 \ No newline at end of file diff --git a/docker/tb-node.env b/docker/tb-node.env index bc0a3f5ee5..e3393d41b1 100644 --- a/docker/tb-node.env +++ b/docker/tb-node.env @@ -10,3 +10,6 @@ REDIS_HOST=redis HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false TB_QUEUE_PARTITIONS_VIRTUAL_NODES_SIZE=64 + +METRICS_ENABLED=true +METRICS_ENDPOINTS_EXPOSE=prometheus From ff14283c2059cc6d7b9622baef2d5225e7143f5a Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 28 Apr 2021 15:13:53 +0300 Subject: [PATCH 102/127] Added default Grafana dashboards --- .../dashboards/attributes_cache.json | 330 ++++++ .../dashboards/core_and_js_metrics.json | 335 ++++++ .../provisioning/dashboards/db_metrics.json | 406 ++++++++ .../dashboards/rule_engine_latency.json | 386 +++++++ .../dashboards/rule_engine_metrics.json | 338 ++++++ .../dashboards/single_service_metrics.json | 982 ++++++++++++++++++ .../dashboards/transport_metrics.json | 532 ++++++++++ 7 files changed, 3309 insertions(+) create mode 100644 docker/monitoring/grafana/provisioning/dashboards/attributes_cache.json create mode 100644 docker/monitoring/grafana/provisioning/dashboards/core_and_js_metrics.json create mode 100644 docker/monitoring/grafana/provisioning/dashboards/db_metrics.json create mode 100644 docker/monitoring/grafana/provisioning/dashboards/rule_engine_latency.json create mode 100644 docker/monitoring/grafana/provisioning/dashboards/rule_engine_metrics.json create mode 100644 docker/monitoring/grafana/provisioning/dashboards/single_service_metrics.json create mode 100644 docker/monitoring/grafana/provisioning/dashboards/transport_metrics.json diff --git a/docker/monitoring/grafana/provisioning/dashboards/attributes_cache.json b/docker/monitoring/grafana/provisioning/dashboards/attributes_cache.json new file mode 100644 index 0000000000..2e21b9cb6e --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/attributes_cache.json @@ -0,0 +1,330 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "increase(attributes_cache_total[1m])", + "interval": "", + "legendFormat": "{{job}} {{result}}", + "queryType": "randomWalk", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Cache Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum (increase(attributes_cache_total{result=\"hit\"}[1m]))", + "interval": "", + "legendFormat": "Hits", + "queryType": "randomWalk", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum (increase(attributes_cache_total{result=\"miss\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "Misses", + "refId": "B" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Summarized Cache Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "100*\n(\n sum(increase(attributes_cache_total{result=\"hit\"}[1m])) / \n ( \n sum(increase(attributes_cache_total{result=\"hit\"}[1m]))\n + sum(increase(attributes_cache_total{result=\"miss\"}[1m]))\n )\n)", + "interval": "", + "legendFormat": "Hit Ratio, %", + "queryType": "randomWalk", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Attributes Cache Hit Ratio %", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Attributes Cache", + "uid": "dxj2OYTMk", + "version": 2 +} \ No newline at end of file diff --git a/docker/monitoring/grafana/provisioning/dashboards/core_and_js_metrics.json b/docker/monitoring/grafana/provisioning/dashboards/core_and_js_metrics.json new file mode 100644 index 0000000000..e04a5c2d84 --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/core_and_js_metrics.json @@ -0,0 +1,335 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 4, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(core_producer_total[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Core Producer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(core_total[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Core Starts", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 10 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(jsInvoke_total[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "JsInvoke Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Core and JS Metrics", + "uid": "lewbrlwjerwkj2", + "version": 2 +} \ No newline at end of file diff --git a/docker/monitoring/grafana/provisioning/dashboards/db_metrics.json b/docker/monitoring/grafana/provisioning/dashboards/db_metrics.json new file mode 100644 index 0000000000..f5983cf304 --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/db_metrics.json @@ -0,0 +1,406 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 7, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(attributes_queue_0_total[1m]))", + "interval": "", + "legendFormat": "queue-0 {{statsName}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(attributes_queue_1_total[1m]))", + "interval": "", + "legendFormat": "queue-1 {{statsName}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(attributes_queue_2_total[1m]))", + "interval": "", + "legendFormat": "queue-2 {{statsName}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (rate(attributes_queue_3_total[1m]))", + "interval": "", + "legendFormat": "queue-3 {{statsName}}", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Attributes", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 11 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_queue_0_total[1m]))", + "interval": "", + "legendFormat": "queue-0 {{statsName}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_queue_1_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-1 {{statsName}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_queue_2_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-2 {{statsName}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_queue_3_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-3 {{statsName}}", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TS", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 11 + }, + "hiddenSeries": false, + "id": 17, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_latest_queue_0_total[1m]))", + "interval": "", + "legendFormat": "queue-0 {{statsName}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_latest_queue_1_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-1 {{statsName}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_latest_queue_2_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-2 {{statsName}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_latest_queue_3_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-3 {{statsName}}", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TS Latest", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "DB Metrics", + "uid": "lewbrlsssswjerwkj", + "version": 2 +} \ No newline at end of file diff --git a/docker/monitoring/grafana/provisioning/dashboards/rule_engine_latency.json b/docker/monitoring/grafana/provisioning/dashboards/rule_engine_latency.json new file mode 100644 index 0000000000..1821eeba41 --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/rule_engine_latency.json @@ -0,0 +1,386 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 5, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (job,quantile) (ruleEngine_Main_seconds)*1000", + "interval": "", + "legendFormat": "Main {{job}} Quantile - {{quantile}}, ms", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum by (job,quantile) (ruleEngine_HighPriority_seconds)*1000", + "hide": false, + "interval": "", + "legendFormat": "HighPriority {{job}} Quantile - {{quantile}}, ms", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by (job,quantile) (ruleEngine_SequentialByOriginator_seconds)*1000", + "hide": false, + "interval": "", + "legendFormat": "SequentialByOriginator {{job}} Quantile - {{quantile}}, ms", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Quantiles Latency, ms", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "ruleEngine_Main_seconds_max *1000", + "interval": "", + "legendFormat": "Max - {{job}}, ms", + "refId": "A" + }, + { + "exemplar": true, + "expr": "ruleEngine_HighPriority_seconds_max *1000", + "hide": false, + "interval": "", + "legendFormat": "HighPriority - {{job}}, ms", + "refId": "B" + }, + { + "exemplar": true, + "expr": "ruleEngine_SequentialByOriginator_seconds_max *1000", + "hide": false, + "interval": "", + "legendFormat": "SequentialByOriginator - {{job}}, ms", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Max Latency, ms", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 5, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "(increase(ruleEngine_Main_seconds_sum[1m]) / increase(ruleEngine_Main_seconds_count[1m])) * 1000", + "interval": "", + "legendFormat": "Main {{job}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "(increase(ruleEngine_HighPriority_seconds_sum[1m]) / increase(ruleEngine_HighPriority_seconds_count[1m])) * 1000", + "hide": false, + "interval": "", + "legendFormat": "HighPriority {{job}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "(increase(ruleEngine_SequentialByOriginator_seconds_sum[1m]) / increase(ruleEngine_SequentialByOriginator_seconds_count[1m])) * 1000", + "hide": false, + "interval": "", + "legendFormat": "SequentialByOriginator {{job}}", + "refId": "C" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Average by 1m", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "10s", + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Rule Engine Latency", + "uid": "-qNMB1SGz", + "version": 2 +} \ No newline at end of file diff --git a/docker/monitoring/grafana/provisioning/dashboards/rule_engine_metrics.json b/docker/monitoring/grafana/provisioning/dashboards/rule_engine_metrics.json new file mode 100644 index 0000000000..9fc1ba2678 --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/rule_engine_metrics.json @@ -0,0 +1,338 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 3, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 12, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName)(increase(ruleEngine_Main_total[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "[Main] Rule Engine Queue Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 12 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName)(increase(ruleEngine_HighPriority_total[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "[HighPriority] Rule Engine Queue Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 12 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName)(increase(ruleEngine_SequentialByOriginator_total[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "[SequentialByOriginator] Rule Engine Queue Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Rule Engine Metrics", + "uid": "lewbrlwjerwkj1", + "version": 2 +} \ No newline at end of file diff --git a/docker/monitoring/grafana/provisioning/dashboards/single_service_metrics.json b/docker/monitoring/grafana/provisioning/dashboards/single_service_metrics.json new file mode 100644 index 0000000000..df33e3efb5 --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/single_service_metrics.json @@ -0,0 +1,982 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 6, + "iteration": 1619603301193, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(core_producer_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Core Producer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 17, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(transport_producer_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Transport Producer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ruleEngine_producer_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Rule Engine Producer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "hiddenSeries": false, + "id": 8, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(core_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Core Starts", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(transport_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Transport Consumer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "hiddenSeries": false, + "id": 15, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName)(increase(ruleEngine_Main_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "[Main] Rule Engine Queue Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 24 + }, + "hiddenSeries": false, + "id": 4, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName)(increase(ruleEngine_HighPriority_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "[HighPriority] Rule Engine Queue Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 24 + }, + "hiddenSeries": false, + "id": 6, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName)(increase(ruleEngine_SequentialByOriginator_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "[SequentialByOriginator] Rule Engine Queue Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 32 + }, + "hiddenSeries": false, + "id": 10, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(jsInvoke_total{job=\"$job\"}[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "JsInvoke Stats", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": false, + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": null, + "current": { + "selected": true, + "text": "tb-mqtt-transport1", + "value": "tb-mqtt-transport1" + }, + "description": null, + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "job", + "options": [ + { + "selected": false, + "text": "tb-core1", + "value": "tb-core1" + }, + { + "selected": false, + "text": "tb-core2", + "value": "tb-core2" + }, + { + "selected": false, + "text": "tb-rule-engine1", + "value": "tb-rule-engine1" + }, + { + "selected": false, + "text": "tb-rule-engine2", + "value": "tb-rule-engine2" + }, + { + "selected": true, + "text": "tb-mqtt-transport1", + "value": "tb-mqtt-transport1" + }, + { + "selected": false, + "text": "tb-mqtt-transport2", + "value": "tb-mqtt-transport2" + }, + { + "selected": false, + "text": "tb-http-transport1", + "value": "tb-http-transport1" + }, + { + "selected": false, + "text": "tb-http-transport2", + "value": "tb-http-transport2" + }, + { + "selected": false, + "text": "tb-coap-transport", + "value": "tb-coap-transport" + } + ], + "query": "tb-core1,tb-core2,tb-rule-engine1,tb-rule-engine2,tb-mqtt-transport1,tb-mqtt-transport2,tb-http-transport1,tb-http-transport2,tb-coap-transport", + "queryValue": "", + "skipUrlSync": false, + "type": "custom" + } + ] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Single Service Metrics", + "uid": "lewbrddddlwjerwkj", + "version": 1 +} \ No newline at end of file diff --git a/docker/monitoring/grafana/provisioning/dashboards/transport_metrics.json b/docker/monitoring/grafana/provisioning/dashboards/transport_metrics.json new file mode 100644 index 0000000000..3027912a59 --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/transport_metrics.json @@ -0,0 +1,532 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 10, + "w": 24, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 19, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum (increase(transport_producer_total{statsName=\"totalMsgs\"}[1m]))", + "interval": "", + "legendFormat": "producer", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum (increase(transport_total{statsName=\"totalMsgs\"}[1m]))", + "hide": false, + "interval": "", + "legendFormat": "consumer", + "refId": "B" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Transport Producer/Consumer Comparison", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "(increase(transport_total[1m]))", + "interval": "", + "legendFormat": "{{job}} - {{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Transport Consumer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "hiddenSeries": false, + "id": 17, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "(increase(transport_producer_total[1m]))", + "interval": "", + "legendFormat": "{{job}} - {{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Transport Producer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "hiddenSeries": false, + "id": 16, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "(increase(core_producer_total[1m]))", + "interval": "", + "legendFormat": "{{job}} - {{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Core Producer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "hiddenSeries": false, + "id": 14, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "(increase(ruleEngine_producer_total[1m]))", + "interval": "", + "legendFormat": "{{job}} - {{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Rule Engine Producer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Transport Metrics", + "uid": "lewbrlwjerwkj", + "version": 4 +} \ No newline at end of file From e618f2382aec55e272fb5f1572876fd77f078f93 Mon Sep 17 00:00:00 2001 From: vzikratyi Date: Wed, 28 Apr 2021 18:28:11 +0300 Subject: [PATCH 103/127] Added dashboard for 'hybrid' DB setup --- .../dashboards/hybrid_db_metrics.json | 474 ++++++++++++++++++ 1 file changed, 474 insertions(+) create mode 100644 docker/monitoring/grafana/provisioning/dashboards/hybrid_db_metrics.json diff --git a/docker/monitoring/grafana/provisioning/dashboards/hybrid_db_metrics.json b/docker/monitoring/grafana/provisioning/dashboards/hybrid_db_metrics.json new file mode 100644 index 0000000000..ed6bd41c00 --- /dev/null +++ b/docker/monitoring/grafana/provisioning/dashboards/hybrid_db_metrics.json @@ -0,0 +1,474 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 8, + "links": [], + "panels": [ + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 12, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(attributes_queue_0_total[1m]))", + "interval": "", + "legendFormat": "queue-0 {{statsName}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(attributes_queue_1_total[1m]))", + "interval": "", + "legendFormat": "queue-1 {{statsName}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(attributes_queue_2_total[1m]))", + "interval": "", + "legendFormat": "queue-2 {{statsName}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (rate(attributes_queue_3_total[1m]))", + "interval": "", + "legendFormat": "queue-3 {{statsName}}", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Attributes", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 0 + }, + "hiddenSeries": false, + "id": 20, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum (rateExecutor_currBuffer)", + "interval": "", + "legendFormat": "current buffer", + "refId": "A" + } + ], + "thresholds": [], + "timeRegions": [], + "title": "Cassandra Current Buffer", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 9 + }, + "hiddenSeries": false, + "id": 18, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by (statsName) (increase(rateExecutor_total[1m]))", + "interval": "", + "legendFormat": "{{statsName}}", + "refId": "A" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TS", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 9 + }, + "hiddenSeries": false, + "id": 17, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.4", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_latest_queue_0_total[1m]))", + "interval": "", + "legendFormat": "queue-0 {{statsName}}", + "refId": "A" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_latest_queue_1_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-1 {{statsName}}", + "refId": "B" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_latest_queue_2_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-2 {{statsName}}", + "refId": "C" + }, + { + "exemplar": true, + "expr": "sum by(statsName) (increase(ts_latest_queue_3_total[1m]))", + "hide": false, + "interval": "", + "legendFormat": "queue-3 {{statsName}}", + "refId": "D" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "TS Latest", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 27, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-15m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Hybrid DB Metrics", + "uid": "lewbrlsssswje", + "version": 2 +} \ No newline at end of file From fbf2d3efdbd66a3cf5f1513439fe220c1c5cdeec Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 29 Apr 2021 14:55:27 +0300 Subject: [PATCH 104/127] Fix entities filtering by kv when search value is empty (#4515) * Fix entities filtering by kv when search value is empty * Refactor --- .../dao/sql/query/EntityKeyMapping.java | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index a759d0de74..caed48758a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -243,23 +243,21 @@ public class EntityKeyMapping { } else { entityTypeStr = "'" + entityType.name() + "'"; } - ctx.addStringParameter(alias + "_key_id", entityKey.getKey()); + ctx.addStringParameter(getKeyId(), entityKey.getKey()); String filterQuery = toQueries(ctx, entityFilter.getType()) .filter(StringUtils::isNotEmpty) .collect(Collectors.joining(" and ")); - if (StringUtils.isEmpty(filterQuery)) { - filterQuery = ""; - } else { + if (StringUtils.isNotEmpty(filterQuery)) { filterQuery = " AND (" + filterQuery + ")"; } if (entityKey.getType().equals(EntityKeyType.TIME_SERIES)) { - String join = hasFilter() ? "inner join" : "left join"; + String join = (hasFilter() && hasFilterValues(ctx)) ? "inner join" : "left join"; return String.format("%s ts_kv_latest %s ON %s.entity_id=entities.id AND %s.key = (select key_id from ts_kv_dictionary where key = :%s_key_id) %s", join, alias, alias, alias, alias, filterQuery); } else { String query; if (!entityKey.getType().equals(EntityKeyType.ATTRIBUTE)) { - String join = hasFilter() ? "inner join" : "left join"; + String join = (hasFilter() && hasFilterValues(ctx)) ? "inner join" : "left join"; query = String.format("%s attribute_kv %s ON %s.entity_id=entities.id AND %s.entity_type=%s AND %s.attribute_key=:%s_key_id ", join, alias, alias, alias, entityTypeStr, alias, alias); String scope; @@ -272,7 +270,7 @@ public class EntityKeyMapping { } query = String.format("%s AND %s.attribute_type='%s' %s", query, alias, scope, filterQuery); } else { - String join = hasFilter() ? "join LATERAL" : "left join LATERAL"; + String join = (hasFilter() && hasFilterValues(ctx)) ? "join LATERAL" : "left join LATERAL"; query = String.format("%s (select * from attribute_kv %s WHERE %s.entity_id=entities.id AND %s.entity_type=%s AND %s.attribute_key=:%s_key_id %s " + "ORDER BY %s.last_update_ts DESC limit 1) as %s ON true", join, alias, alias, alias, entityTypeStr, alias, alias, filterQuery, alias, alias); @@ -281,15 +279,26 @@ public class EntityKeyMapping { } } + private boolean hasFilterValues(QueryContext ctx) { + return Arrays.stream(ctx.getParameterNames()).anyMatch(parameterName -> { + return !parameterName.equals(getKeyId()) && parameterName.startsWith(alias); + }); + } + + private String getKeyId() { + return alias + "_key_id"; + } + public static String buildSelections(List mappings, EntityFilterType filterType, EntityType entityType) { return mappings.stream().map(mapping -> mapping.toSelection(filterType, entityType)).collect( Collectors.joining(", ")); } public static String buildLatestJoins(QueryContext ctx, EntityFilter entityFilter, EntityType entityType, List latestMappings, boolean countQuery) { - return latestMappings.stream().filter(mapping -> !countQuery || mapping.hasFilter()) - .map(mapping -> mapping.toLatestJoin(ctx, entityFilter, entityType)).collect( - Collectors.joining(" ")); + return latestMappings.stream() + .filter(mapping -> !countQuery || mapping.hasFilter()) + .map(mapping -> mapping.toLatestJoin(ctx, entityFilter, entityType)) + .collect(Collectors.joining(" ")); } public static String buildQuery(QueryContext ctx, List mappings, EntityFilterType filterType) { From 52c34068aef87f1c5a9bdb35a93daa1cd95d5743 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 29 Apr 2021 16:55:05 +0300 Subject: [PATCH 105/127] UI: Move Resource library to system settings menu --- ui-ngx/src/app/core/services/menu.service.ts | 94 ++++++++++--------- .../home/pages/admin/admin-routing.module.ts | 26 +++-- .../modules/home/pages/admin/admin.module.ts | 4 +- .../resources-library-table-config.resolve.ts | 2 +- .../resource/resources-library.component.html | 0 .../resource/resources-library.component.ts | 0 .../modules/home/pages/home-pages.module.ts | 2 - .../pages/resource/resource-routing.module.ts | 48 ---------- .../home/pages/resource/resource.module.ts | 33 ------- .../assets/locale/locale.constant-en_US.json | 1 - 10 files changed, 75 insertions(+), 135 deletions(-) rename ui-ngx/src/app/modules/home/pages/{ => admin}/resource/resources-library-table-config.resolve.ts (98%) rename ui-ngx/src/app/modules/home/pages/{ => admin}/resource/resources-library.component.html (100%) rename ui-ngx/src/app/modules/home/pages/{ => admin}/resource/resources-library.component.ts (100%) delete mode 100644 ui-ngx/src/app/modules/home/pages/resource/resource-routing.module.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/resource/resource.module.ts diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index f1f286ca16..07a6d8f237 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -103,19 +103,12 @@ export class MenuService { path: '/widgets-bundles', icon: 'now_widgets' }, - { - id: guid(), - name: 'resource.resources-library', - type: 'link', - path: '/resources-library', - icon: 'folder' - }, { id: guid(), name: 'admin.system-settings', type: 'toggle', path: '/settings', - height: '200px', + height: '240px', icon: 'settings', pages: [ { @@ -152,6 +145,13 @@ export class MenuService { type: 'link', path: '/settings/oauth2', icon: 'security' + }, + { + id: guid(), + name: 'resource.resources-library', + type: 'link', + path: '/settings/resources-library', + icon: 'folder' } ] } @@ -188,16 +188,6 @@ export class MenuService { } ] }, - { - name: 'resource.management', - places: [ - { - name: 'resource.resources-library', - icon: 'folder', - path: '/resources-library' - } - ] - }, { name: 'admin.system-settings', places: [ @@ -225,6 +215,11 @@ export class MenuService { name: 'admin.oauth2.oauth2', icon: 'security', path: '/settings/oauth2' + }, + { + name: 'resource.resources-library', + icon: 'folder', + path: '/resources-library' } ] } @@ -337,20 +332,6 @@ export class MenuService { path: '/dashboards', icon: 'dashboards' }, - { - id: guid(), - name: 'resource.resources-library', - type: 'link', - path: '/resources-library', - icon: 'folder' - }, - { - id: guid(), - name: 'admin.home-settings', - type: 'link', - path: '/settings/home', - icon: 'settings_applications' - }, { id: guid(), name: 'audit-log.audit-logs', @@ -365,6 +346,30 @@ export class MenuService { path: '/usage', icon: 'insert_chart', notExact: true + }, + { + id: guid(), + name: 'admin.system-settings', + type: 'toggle', + path: '/settings', + height: '80px', + icon: 'settings', + pages: [ + { + id: guid(), + name: 'admin.home-settings', + type: 'link', + path: '/settings/home', + icon: 'settings_applications' + }, + { + id: guid(), + name: 'resource.resources-library', + type: 'link', + path: '/settings/resources-library', + icon: 'folder' + } + ] } ); return sections; @@ -455,16 +460,6 @@ export class MenuService { ); } homeSections.push( - { - name: 'resource.management', - places: [ - { - name: 'resource.resources-library', - icon: 'folder', - path: '/resources-library' - } - ] - }, { name: 'dashboard.management', places: [ @@ -494,6 +489,21 @@ export class MenuService { path: '/usage' } ] + }, + { + name: 'admin.system-settings', + places: [ + { + name: 'admin.home-settings', + icon: 'settings_applications', + path: '/settings/home' + }, + { + name: 'resource.resources-library', + icon: 'folder', + path: '/settings/resources-library' + } + ] } ); return homeSections; 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 1928a1f404..8de41af9c2 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 @@ -23,16 +23,12 @@ import { Authority } from '@shared/models/authority.enum'; import { GeneralSettingsComponent } from '@modules/home/pages/admin/general-settings.component'; import { SecuritySettingsComponent } from '@modules/home/pages/admin/security-settings.component'; import { OAuth2SettingsComponent } from '@home/pages/admin/oauth2-settings.component'; -import { User } from '@shared/models/user.model'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { UserService } from '@core/http/user.service'; 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'; import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; +import { ResourcesLibraryTableConfigResolver } from '@home/pages/admin/resource/resources-library-table-config.resolve'; @Injectable() export class OAuth2LoginProcessingUrlResolver implements Resolve { @@ -146,6 +142,21 @@ const routes: Routes = [ icon: 'settings_applications' } } + }, + { + path: 'resources-library', + component: EntitiesTableComponent, + data: { + auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], + title: 'resource.resources-library', + breadcrumb: { + label: 'resource.resources-library', + icon: 'folder' + } + }, + resolve: { + entitiesTableConfig: ResourcesLibraryTableConfigResolver + } } ] } @@ -155,7 +166,8 @@ const routes: Routes = [ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [ - OAuth2LoginProcessingUrlResolver + OAuth2LoginProcessingUrlResolver, + ResourcesLibraryTableConfigResolver ] }) export class AdminRoutingModule { } 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 1671220cfe..d97471a2a0 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 @@ -27,6 +27,7 @@ import { OAuth2SettingsComponent } from '@modules/home/pages/admin/oauth2-settin import { SmsProviderComponent } from '@home/pages/admin/sms-provider.component'; import { SendTestSmsDialogComponent } from '@home/pages/admin/send-test-sms-dialog.component'; import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component'; +import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component'; @NgModule({ declarations: @@ -37,7 +38,8 @@ import { HomeSettingsComponent } from '@home/pages/admin/home-settings.component SendTestSmsDialogComponent, SecuritySettingsComponent, OAuth2SettingsComponent, - HomeSettingsComponent + HomeSettingsComponent, + ResourcesLibraryComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts similarity index 98% rename from ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts rename to ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index d6759310ac..089d4b3d82 100644 --- a/ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -32,7 +32,7 @@ import { getCurrentAuthUser } from '@core/auth/auth.selectors'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { Authority } from '@shared/models/authority.enum'; -import { ResourcesLibraryComponent } from '@home/pages/resource/resources-library.component'; +import { ResourcesLibraryComponent } from '@home/pages/admin/resource/resources-library.component'; import { PageLink } from '@shared/models/page/page-link'; import { EntityAction } from '@home/models/entity/entity-component.models'; import { map } from 'rxjs/operators'; diff --git a/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.html b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html similarity index 100% rename from ui-ngx/src/app/modules/home/pages/resource/resources-library.component.html rename to ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html diff --git a/ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts similarity index 100% rename from ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts rename to ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts diff --git a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts index 774eccf62f..050b71db83 100644 --- a/ui-ngx/src/app/modules/home/pages/home-pages.module.ts +++ b/ui-ngx/src/app/modules/home/pages/home-pages.module.ts @@ -35,7 +35,6 @@ import { modulesMap } from '../../common/modules-map'; import { DeviceProfileModule } from './device-profile/device-profile.module'; import { ApiUsageModule } from '@home/pages/api-usage/api-usage.module'; import { EdgeModule } from '@home/pages/edge/edge.module'; -import { ResourceModule } from '@home/pages/resource/resource.module'; import { FirmwareModule } from '@home/pages/firmware/firmware.module'; @NgModule({ @@ -56,7 +55,6 @@ import { FirmwareModule } from '@home/pages/firmware/firmware.module'; DashboardModule, AuditLogModule, ApiUsageModule, - ResourceModule, FirmwareModule, UserModule ], diff --git a/ui-ngx/src/app/modules/home/pages/resource/resource-routing.module.ts b/ui-ngx/src/app/modules/home/pages/resource/resource-routing.module.ts deleted file mode 100644 index 4eab81e2f2..0000000000 --- a/ui-ngx/src/app/modules/home/pages/resource/resource-routing.module.ts +++ /dev/null @@ -1,48 +0,0 @@ -/// -/// Copyright © 2016-2021 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 { RouterModule, Routes } from '@angular/router'; -import { EntitiesTableComponent } from '@home/components/entity/entities-table.component'; -import { Authority } from '@shared/models/authority.enum'; -import { NgModule } from '@angular/core'; -import { ResourcesLibraryTableConfigResolver } from './resources-library-table-config.resolve'; - -const routes: Routes = [ - { - path: 'resources-library', - component: EntitiesTableComponent, - data: { - auth: [Authority.TENANT_ADMIN, Authority.SYS_ADMIN], - title: 'resource.resources-library', - breadcrumb: { - label: 'resource.resources-library', - icon: 'folder' - } - }, - resolve: { - entitiesTableConfig: ResourcesLibraryTableConfigResolver - } - } -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule], - providers: [ - ResourcesLibraryTableConfigResolver - ] -}) -export class ResourcesLibraryRoutingModule{ } diff --git a/ui-ngx/src/app/modules/home/pages/resource/resource.module.ts b/ui-ngx/src/app/modules/home/pages/resource/resource.module.ts deleted file mode 100644 index 4cf31af12c..0000000000 --- a/ui-ngx/src/app/modules/home/pages/resource/resource.module.ts +++ /dev/null @@ -1,33 +0,0 @@ -/// -/// Copyright © 2016-2021 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 { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { ResourcesLibraryRoutingModule } from '@home/pages/resource/resource-routing.module'; -import { SharedModule } from '@shared/shared.module'; -import { HomeComponentsModule } from '@home/components/home-components.module'; -import { ResourcesLibraryComponent } from './resources-library.component'; - -@NgModule({ - declarations: [ResourcesLibraryComponent], - imports: [ - CommonModule, - SharedModule, - HomeComponentsModule, - ResourcesLibraryRoutingModule - ] -}) -export class ResourceModule { } 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 08335a0175..313919c4f6 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2238,7 +2238,6 @@ "drop-file": "Drop a resource file or click to select a file to upload.", "empty": "Resource is empty", "export": "Export resource", - "management": "Resource management", "no-resource-matching": "No resource matching '{{widgetsBundle}}' were found.", "no-resource-text": "No resources found", "open-widgets-bundle": "Open widgets bundle", From 99a25cab00a38bcff520f25ca1c2c1cda0f25cb2 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 29 Apr 2021 18:25:23 +0300 Subject: [PATCH 106/127] UI: Fixed field name in LC_EVENT; Added error field reset at hiding in event filters (#4453) * UI: Fixed field name in LC_EVENT; Added error field reset at hiding in event filters * UI: Updated event filter interface * UI: Add reset page in event filter --- .../components/event/event-filter-panel.component.html | 3 ++- .../components/event/event-filter-panel.component.ts | 6 ++++++ .../home/components/event/event-table-config.ts | 3 ++- ui-ngx/src/app/shared/models/event.models.ts | 10 ++++++---- 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html index 6a017f214e..579301d086 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html @@ -39,7 +39,8 @@
- + {{ 'event.has-error' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts index f60f7a09f7..954253b459 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts @@ -105,5 +105,11 @@ export class EventFilterPanelComponent { cancel() { this.overlayRef.dispose(); } + + changeIsError(value: boolean | string) { + if (this.conditionError && value === '') { + this.eventFilterFormGroup.get('error').reset('', {emitEvent: false}); + } + } } diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 1dc663684f..8caa6c7aab 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -288,7 +288,7 @@ export class EventTableConfig extends EntityTableConfig { break; case EventType.LC_EVENT: this.filterColumns.push( - {key: 'method', title: 'event.event'}, + {key: 'event', title: 'event.event'}, {key: 'status', title: 'event.status'}, {key: 'error', title: 'event.error'} ); @@ -356,6 +356,7 @@ export class EventTableConfig extends EntityTableConfig { componentRef.onDestroy(() => { if (componentRef.instance.result && !isEqual(this.filterParams, componentRef.instance.result.filterParams)) { this.filterParams = componentRef.instance.result.filterParams; + this.table.paginator.pageIndex = 0; this.table.updateData(); } }); diff --git a/ui-ngx/src/app/shared/models/event.models.ts b/ui-ngx/src/app/shared/models/event.models.ts index b801bab750..ac7d87c0c4 100644 --- a/ui-ngx/src/app/shared/models/event.models.ts +++ b/ui-ngx/src/app/shared/models/event.models.ts @@ -91,17 +91,18 @@ export interface BaseFilterEventBody { export interface ErrorFilterEventBody extends BaseFilterEventBody { method?: string; + error?: string; } export interface LcFilterEventEventBody extends BaseFilterEventBody { - method?: string; + event?: string; status?: string; - isError?: boolean; + error?: string; } export interface StatsFilterEventBody extends BaseFilterEventBody { - messagesProcessed?: string; - errorsOccurred?: string; + messagesProcessed?: number; + errorsOccurred?: number; } export interface DebugFilterRuleNodeEventBody extends BaseFilterEventBody { @@ -114,6 +115,7 @@ export interface DebugFilterRuleNodeEventBody extends BaseFilterEventBody { dataSearch?: string; metadataSearch?: string; isError?: boolean; + error?: string; } export type FilterEventBody = ErrorFilterEventBody & LcFilterEventEventBody & StatsFilterEventBody & DebugFilterRuleNodeEventBody; From 9dcb7b9b5bc13c39b7531322259ee90ceb52050c Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Thu, 29 Apr 2021 18:25:38 +0300 Subject: [PATCH 107/127] UI: Refactoring for the build rule node ui (#4460) * UI: Refactoring LWM2M; fixed build ui rule node * UI: Refactoring LWM2M security config --- .../device/device-credentials.component.html | 4 +- .../device/device-credentials.component.ts | 75 ++-- ...ecurity-config-lwm2m-server.component.html | 79 ++++ ...ecurity-config-lwm2m-server.component.scss | 20 + .../security-config-lwm2m-server.component.ts | 176 ++++++++ .../security-config-lwm2m.component.html | 143 +++++++ .../security-config-lwm2m.component.scss | 34 ++ .../device/security-config-lwm2m.component.ts | 257 ++++++++++++ .../home/components/home-components.module.ts | 6 + .../lwm2m-attributes-dialog.component.ts | 27 +- .../lwm2m-attributes-key-list.component.ts | 30 +- .../lwm2m/lwm2m-attributes.component.ts | 41 +- .../lwm2m-device-config-server.component.ts | 18 +- ...ofile-transport-configuration.component.ts | 31 +- ...m-object-add-instances-dialog.component.ts | 14 +- ...m2m-object-add-instances-list.component.ts | 11 +- .../lwm2m/lwm2m-object-list.component.ts | 47 +-- ...serve-attr-telemetry-resource.component.ts | 19 +- .../lwm2m-observe-attr-telemetry.component.ts | 24 +- .../lwm2m/lwm2m-profile-components.module.ts | 26 +- .../lwm2m/lwm2m-profile-config.models.ts | 4 +- .../home/pages/device/device.module.ts | 6 +- .../security-config-server.component.html | 101 ----- .../lwm2m/security-config-server.component.ts | 142 ------- .../lwm2m/security-config.component.html | 180 --------- .../device/lwm2m/security-config.component.ts | 382 ------------------ .../models/lwm2m-security-config.models.ts} | 66 ++- .../assets/locale/locale.constant-en_US.json | 15 +- 28 files changed, 925 insertions(+), 1053 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.html create mode 100644 ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html create mode 100644 ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html delete mode 100644 ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts delete mode 100644 ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html delete mode 100644 ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.ts rename ui-ngx/src/app/{modules/home/pages/device/lwm2m/security-config.models.ts => shared/models/lwm2m-security-config.models.ts} (60%) diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html index faa2dbbce4..db28ff0453 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html @@ -20,7 +20,7 @@ device.credentials-type - {{ credentialTypeNamesMap.get(deviceCredentialsType[credentialsType]) }} + {{ credentialTypeNamesMap.get(credentialsType) }} @@ -85,7 +85,7 @@ device.lwm2m-value diff --git a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts index edc331d338..17ad828b48 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import {Component, forwardRef, Input, OnDestroy, OnInit} from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; import { ControlValueAccessor, FormBuilder, @@ -33,9 +33,9 @@ import { DeviceCredentials, DeviceCredentialsType } from '@shared/models/device.models'; -import {Subscription} from 'rxjs'; -import {distinctUntilChanged} from 'rxjs/operators'; -import {SecurityConfigComponent} from '@home/pages/device/lwm2m/security-config.component'; +import { Subject } from 'rxjs'; +import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; +import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; import { ClientSecurityConfig, DEFAULT_END_POINT, @@ -44,10 +44,10 @@ import { getDefaultSecurityConfig, JSON_ALL_CONFIG, validateSecurityConfig -} from '@home/pages/device/lwm2m/security-config.models'; -import {TranslateService} from '@ngx-translate/core'; -import {MatDialog} from '@angular/material/dialog'; -import {isDefinedAndNotNull} from '@core/utils'; +} from '@shared/models/lwm2m-security-config.models'; +import { TranslateService } from '@ngx-translate/core'; +import { MatDialog } from '@angular/material/dialog'; +import { isDefinedAndNotNull } from '@core/utils'; @Component({ selector: 'tb-device-credentials', @@ -67,20 +67,16 @@ import {isDefinedAndNotNull} from '@core/utils'; }) export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, Validator, OnDestroy { - deviceCredentialsFormGroup: FormGroup; - - subscriptions: Subscription[] = []; - @Input() disabled: boolean; - deviceCredentials: DeviceCredentials = null; + private destroy$ = new Subject(); - submitted = false; + deviceCredentialsFormGroup: FormGroup; deviceCredentialsType = DeviceCredentialsType; - credentialsTypes = Object.keys(DeviceCredentialsType); + credentialsTypes = Object.values(DeviceCredentialsType); credentialTypeNamesMap = credentialTypeNames; @@ -102,16 +98,17 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, }, {validators: this.atLeastOne(Validators.required, ['clientId', 'userName'])}) }); this.deviceCredentialsFormGroup.get('credentialsBasic').disable(); - this.subscriptions.push( - this.deviceCredentialsFormGroup.valueChanges.pipe(distinctUntilChanged()).subscribe(() => { - this.updateView(); - }) - ); - this.subscriptions.push( - this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.subscribe(() => { - this.credentialsTypeChanged(); - }) - ); + this.deviceCredentialsFormGroup.valueChanges.pipe( + distinctUntilChanged(), + takeUntil(this.destroy$) + ).subscribe(() => { + this.updateView(); + }); + this.deviceCredentialsFormGroup.get('credentialsType').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((type) => { + this.credentialsTypeChanged(type); + }); } ngOnInit(): void { @@ -121,12 +118,12 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, } ngOnDestroy() { - this.subscriptions.forEach(s => s.unsubscribe()); + this.destroy$.next(); + this.destroy$.complete(); } writeValue(value: DeviceCredentials | null): void { if (isDefinedAndNotNull(value)) { - this.deviceCredentials = value; let credentialsBasic = {clientId: null, userName: null, password: null}; let credentialsValue = null; if (value.credentialsType === DeviceCredentialsType.MQTT_BASIC) { @@ -179,10 +176,11 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, }; } - credentialsTypeChanged(): void { + credentialsTypeChanged(credentialsType: DeviceCredentialsType): void { + const credentialsValue = credentialsType === DeviceCredentialsType.LWM2M_CREDENTIALS ? this.lwm2mDefaultConfig : null; this.deviceCredentialsFormGroup.patchValue({ credentialsId: null, - credentialsValue: JSON.stringify(getDefaultSecurityConfig(), null, 2), + credentialsValue, credentialsBasic: {clientId: '', userName: '', password: ''} }); this.updateValidators(); @@ -264,7 +262,7 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, } } const credentialsId = this.deviceCredentialsFormGroup.get('credentialsId').value || DEFAULT_END_POINT; - this.dialog.open(SecurityConfigComponent, { + this.dialog.open(SecurityConfigLwm2mComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { @@ -275,8 +273,8 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, (res) => { if (res) { this.deviceCredentialsFormGroup.patchValue({ - credentialsValue: this.isDefautLw2mResponse(res[JSON_ALL_CONFIG]) ? null : JSON.stringify(res[JSON_ALL_CONFIG]), - credentialsId: this.isDefautLw2mResponse(res[END_POINT]) ? null : JSON.stringify(res[END_POINT]).split('\"').join('') + credentialsValue: this.isDefaultLw2mResponse(res[JSON_ALL_CONFIG]) ? null : JSON.stringify(res[JSON_ALL_CONFIG]), + credentialsId: this.isDefaultLw2mResponse(res[END_POINT]) ? null : JSON.stringify(res[END_POINT]).split('\"').join('') }); this.deviceCredentialsFormGroup.get('credentialsValue').markAsDirty(); } @@ -284,16 +282,19 @@ export class DeviceCredentialsComponent implements ControlValueAccessor, OnInit, ); } - private isDefautLw2mResponse(response: object): boolean { + private isDefaultLw2mResponse(response: object): boolean { return Object.keys(response).length === 0 || JSON.stringify(response) === '[{}]'; } private lwm2mConfigJsonValidator(control: FormControl) { - return validateSecurityConfig(control.value) ? null: {jsonError: {parsedJson: "error"}}; + return validateSecurityConfig(control.value) ? null : {jsonError: {parsedJson: 'error'}}; + } + + private get lwm2mDefaultConfig(): string { + return JSON.stringify(getDefaultSecurityConfig(), null, 2); } - lwm2mCredentialsValueTip (flag: boolean): string { - let jsonConfigDef = JSON.stringify(getDefaultSecurityConfig(), null, 2); - return !flag ? "" : 'Example (mode=\"NoSec\"):\n\r ' + jsonConfigDef; + lwm2mCredentialsValueTooltip(flag: boolean): string { + return !flag ? '' : 'Example (mode=\"NoSec\"):\n\r ' + this.lwm2mDefaultConfig; } } diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.html b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.html new file mode 100644 index 0000000000..a40ce4cb69 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.html @@ -0,0 +1,79 @@ + +
+ + device.lwm2m-security-config.mode + + + {{ lwm2mSecurityTypeTranslationMap.get(securityType) }} + + + +
+ + {{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }} + + {{clientPublicKeyOrId.value?.length || 0}}/{{lenMaxClientPublicKeyOrId}} + + {{ 'device.lwm2m-security-config.client-publicKey-or-id-required' | translate }} + + + {{ 'device.lwm2m-security-config.client-publicKey-or-id-pattern' | translate }} + + + {{ 'device.lwm2m-security-config.client-publicKey-or-id-length' | translate: { + count: lenMaxClientPublicKeyOrId + } }} + + + + {{ 'device.lwm2m-security-config.client-secret-key' | translate }} + + {{clientSecretKey.value?.length || 0}}/{{lengthClientSecretKey}} + + {{ 'device.lwm2m-security-config.client-secret-key-required' | translate }} + + + {{ 'device.lwm2m-security-config.client-secret-key-pattern' | translate }} + + + {{ 'device.lwm2m-security-config.client-secret-key-length' | translate: { + count: lengthClientSecretKey + } }} + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss new file mode 100644 index 0000000000..1c55a86d8b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.scss @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2021 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 ::ng-deep { + textarea.mat-input-element.cdk-textarea-autosize { + box-sizing: content-box; + } +} diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts new file mode 100644 index 0000000000..b8e1b42ef4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m-server.component.ts @@ -0,0 +1,176 @@ +/// +/// Copyright © 2016-2021 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, OnDestroy } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { + KEY_REGEXP_HEX_DEC, + LEN_MAX_PRIVATE_KEY, + LEN_MAX_PSK, + LEN_MAX_PUBLIC_KEY_RPK, + LEN_MAX_PUBLIC_KEY_X509, + Lwm2mSecurityType, + Lwm2mSecurityTypeTranslationMap, + ServerSecurityConfig +} from '@shared/models/lwm2m-security-config.models'; +import { takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +@Component({ + selector: 'tb-security-config-lwm2m-server', + templateUrl: './security-config-lwm2m-server.component.html', + styleUrls: ['./security-config-lwm2m-server.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SecurityConfigLwm2mServerComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SecurityConfigLwm2mServerComponent), + multi: true + } + ] +}) + +export class SecurityConfigLwm2mServerComponent implements OnDestroy, ControlValueAccessor, Validator { + + serverFormGroup: FormGroup; + securityConfigLwM2MType = Lwm2mSecurityType; + securityConfigLwM2MTypes = Object.values(Lwm2mSecurityType); + lwm2mSecurityTypeTranslationMap = Lwm2mSecurityTypeTranslationMap; + lenMinClientPublicKeyOrId = 0; + lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; + lengthClientSecretKey = LEN_MAX_PRIVATE_KEY; + + private destroy$ = new Subject(); + private propagateChange = (v: any) => {}; + + constructor(private fb: FormBuilder) { + this.serverFormGroup = this.fb.group({ + securityMode: [Lwm2mSecurityType.NO_SEC], + clientPublicKeyOrId: [''], + clientSecretKey: [''] + }); + this.serverFormGroup.get('securityMode').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((securityMode) => { + this.updateValidate(securityMode); + }); + + this.serverFormGroup.valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((value) => { + this.propagateChange(value); + }); + } + + writeValue(value: any): void { + if (value) { + this.updateValueFields(value); + } + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.serverFormGroup.disable({emitEvent: false}); + } else { + this.serverFormGroup.enable({emitEvent: false}); + } + } + + validate(control): ValidationErrors | null { + return this.serverFormGroup.valid ? null : { + securityConfig: {valid: false} + }; + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + private updateValueFields(serverData: ServerSecurityConfig): void { + this.serverFormGroup.patchValue(serverData, {emitEvent: false}); + this.updateValidate(serverData.securityMode, true); + } + + private updateValidate(securityMode: Lwm2mSecurityType, initValue = false): void { + switch (securityMode) { + case Lwm2mSecurityType.NO_SEC: + this.serverFormGroup.get('clientPublicKeyOrId').clearValidators(); + this.serverFormGroup.get('clientSecretKey').clearValidators(); + break; + case Lwm2mSecurityType.PSK: + this.lenMinClientPublicKeyOrId = 0; + this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; + this.lengthClientSecretKey = LEN_MAX_PSK; + this.setValidatorsSecurity(securityMode); + break; + case Lwm2mSecurityType.RPK: + this.lenMinClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; + this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; + this.lengthClientSecretKey = LEN_MAX_PRIVATE_KEY; + this.setValidatorsSecurity(securityMode); + break; + case Lwm2mSecurityType.X509: + this.lenMinClientPublicKeyOrId = 0; + this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_X509; + this.lengthClientSecretKey = LEN_MAX_PRIVATE_KEY; + this.setValidatorsSecurity(securityMode); + break; + } + this.serverFormGroup.get('clientPublicKeyOrId').updateValueAndValidity({emitEvent: false}); + this.serverFormGroup.get('clientSecretKey').updateValueAndValidity({emitEvent: !initValue}); + } + + private setValidatorsSecurity = (securityMode: Lwm2mSecurityType): void => { + if (securityMode === Lwm2mSecurityType.PSK) { + this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required]); + } else { + this.serverFormGroup.get('clientPublicKeyOrId').setValidators([ + Validators.required, + Validators.pattern(KEY_REGEXP_HEX_DEC), + Validators.minLength(this.lenMinClientPublicKeyOrId), + Validators.maxLength(this.lenMaxClientPublicKeyOrId) + ]); + } + + this.serverFormGroup.get('clientSecretKey').setValidators([ + Validators.required, + Validators.pattern(KEY_REGEXP_HEX_DEC), + Validators.minLength(this.lengthClientSecretKey), + Validators.maxLength(this.lengthClientSecretKey) + ]); + } +} diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html new file mode 100644 index 0000000000..bd83cc817b --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.html @@ -0,0 +1,143 @@ + +
+ +

{{ title }}

+ + +
+
+ + device.lwm2m-security-config.endpoint + + + {{ 'device.lwm2m-security-config.endpoint-required' | translate }} + + + + + + device.lwm2m-security-config.mode + + + {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} + + + +
+ + {{ 'device.lwm2m-security-config.identity' | translate }} + + + {{ 'device.lwm2m-security-config.identity-required' | translate }} + + +
+ + {{ 'device.lwm2m-security-config.client-key' | translate }} + + {{key.value?.length || 0}}/{{lenMaxKeyClient}} + + {{ 'device.lwm2m-security-config.client-key-required' | translate }} + + + {{ 'device.lwm2m-security-config.client-key-pattern' | translate }} + + + {{ 'device.lwm2m-security-config.client-key-length' | translate: { + count: lenMaxKeyClient + } }} + + + + {{ 'device.lwm2m-security-config.client-certificate' | translate }} + +
+ +
+ + + + + {{ 'device.lwm2m-security-config.bootstrap-server' | translate }} + + + + + + + + + + + {{ 'device.lwm2m-security-config.lwm2m-server' | translate }} + + + + + + + + +
+
+ + + + + + +
+
+
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss new file mode 100644 index 0000000000..0188fcd559 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.scss @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2021 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 { + mat-tab-group { + min-height: 330px; + } +} + +:host ::ng-deep { + .mat-tab-body-wrapper { + min-height: 200px; + } + + .mat-tab-body { + padding: 16px 0; + } + + textarea.mat-input-element.cdk-textarea-autosize { + box-sizing: content-box; + } +} diff --git a/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts new file mode 100644 index 0000000000..419576753c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/device/security-config-lwm2m.component.ts @@ -0,0 +1,257 @@ +/// +/// Copyright © 2016-2021 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, OnDestroy, OnInit } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { TranslateService } from '@ngx-translate/core'; +import { + DeviceCredentialsDialogLwm2mData, + getClientSecurityConfig, + JSON_ALL_CONFIG, + KEY_REGEXP_HEX_DEC, + LEN_MAX_PSK, + LEN_MAX_PUBLIC_KEY_RPK, + Lwm2mSecurityConfigModels, + Lwm2mSecurityType, + Lwm2mSecurityTypeTranslationMap +} from '@shared/models/lwm2m-security-config.models'; +import { MatTabChangeEvent } from '@angular/material/tabs'; +import { MatTab } from '@angular/material/tabs/tab'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-security-config-lwm2m', + templateUrl: './security-config-lwm2m.component.html', + styleUrls: ['./security-config-lwm2m.component.scss'] +}) + +export class SecurityConfigLwm2mComponent extends DialogComponent implements OnInit, OnDestroy { + + private destroy$ = new Subject(); + + lwm2mConfigFormGroup: FormGroup; + title: string; + securityConfigLwM2MType = Lwm2mSecurityType; + securityConfigLwM2MTypes = Object.keys(Lwm2mSecurityType); + credentialTypeLwM2MNamesMap = Lwm2mSecurityTypeTranslationMap; + formControlNameJsonAllConfig = JSON_ALL_CONFIG; + jsonAllConfig: Lwm2mSecurityConfigModels; + lenMaxKeyClient = LEN_MAX_PSK; + tabPrevious: MatTab; + tabIndexPrevious = 0; + + constructor(protected store: Store, + protected router: Router, + @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, + public dialogRef: MatDialogRef, + public fb: FormBuilder, + public translate: TranslateService) { + super(store, router, dialogRef); + } + + ngOnInit() { + this.jsonAllConfig = JSON.parse(JSON.stringify(this.data.jsonAllConfig)); + this.lwm2mConfigFormGroup = this.initLwm2mConfigFormGroup(); + this.title = this.translate.instant('device.lwm2m-security-info') + ': ' + this.data.endPoint; + this.lwm2mConfigFormGroup.get('x509').disable(); + this.initClientSecurityConfig(this.lwm2mConfigFormGroup.get('jsonAllConfig').value); + this.registerDisableOnLoadFormControl(this.lwm2mConfigFormGroup.get('securityConfigClientMode')); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + private initClientSecurityConfig = (jsonAllConfig: Lwm2mSecurityConfigModels): void => { + if (jsonAllConfig.client.securityConfigClientMode !== Lwm2mSecurityType.NO_SEC) { + this.lwm2mConfigFormGroup.patchValue(jsonAllConfig.client, {emitEvent: false}); + } + this.securityConfigClientUpdateValidators(jsonAllConfig.client.securityConfigClientMode); + } + + private securityConfigClientModeChanged(type: Lwm2mSecurityType): void { + const config = getClientSecurityConfig(type, this.lwm2mConfigFormGroup.get('endPoint').value); + switch (type) { + case Lwm2mSecurityType.PSK: + config.identity = this.data.endPoint; + config.key = this.lwm2mConfigFormGroup.get('key').value; + break; + case Lwm2mSecurityType.RPK: + config.key = this.lwm2mConfigFormGroup.get('key').value; + break; + } + this.jsonAllConfig.client = config; + this.lwm2mConfigFormGroup.patchValue({ + ...config, + jsonAllConfig: this.jsonAllConfig + }, {emitEvent: false}); + this.securityConfigClientUpdateValidators(type); + } + + private securityConfigClientUpdateValidators = (mode: Lwm2mSecurityType): void => { + switch (mode) { + case Lwm2mSecurityType.NO_SEC: + case Lwm2mSecurityType.X509: + this.setValidatorsNoSecX509(); + break; + case Lwm2mSecurityType.PSK: + this.lenMaxKeyClient = LEN_MAX_PSK; + this.setValidatorsPskRpk(mode); + break; + case Lwm2mSecurityType.RPK: + this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; + this.setValidatorsPskRpk(mode); + break; + } + this.lwm2mConfigFormGroup.get('identity').updateValueAndValidity({emitEvent: false}); + this.lwm2mConfigFormGroup.get('key').updateValueAndValidity({emitEvent: false}); + } + + private setValidatorsNoSecX509 = (): void => { + this.lwm2mConfigFormGroup.get('identity').setValidators([]); + this.lwm2mConfigFormGroup.get('key').setValidators([]); + } + + private setValidatorsPskRpk = (mode: Lwm2mSecurityType): void => { + if (mode === Lwm2mSecurityType.PSK) { + this.lwm2mConfigFormGroup.get('identity').setValidators([Validators.required]); + } else { + this.lwm2mConfigFormGroup.get('identity').setValidators([]); + } + this.lwm2mConfigFormGroup.get('key').setValidators([Validators.required, + Validators.pattern(KEY_REGEXP_HEX_DEC), + Validators.maxLength(this.lenMaxKeyClient), Validators.minLength(this.lenMaxKeyClient)]); + } + + tabChanged = (tabChangeEvent: MatTabChangeEvent): void => { + if (this.tabIndexPrevious !== tabChangeEvent.index) { + this.upDateValueToJson(); + } + this.tabIndexPrevious = tabChangeEvent.index; + } + + private upDateValueToJson(): void { + switch (this.tabIndexPrevious) { + case 0: + this.upDateValueToJsonTab0(); + break; + case 1: + this.upDateValueToJsonTab1(); + break; + } + } + + private upDateValueToJsonTab0 = (): void => { + if (this.lwm2mConfigFormGroup.get('identity').dirty && this.lwm2mConfigFormGroup.get('identity').valid || + this.lwm2mConfigFormGroup.get('key').dirty && this.lwm2mConfigFormGroup.get('key').valid) { + this.updateBootstrapSettings(); + this.upDateJsonAllConfig(); + } + } + + private upDateValueToJsonTab1 = (): void => { + const bootstrap = this.lwm2mConfigFormGroup.get('bootstrapServer').value; + if (bootstrap !== null + && this.lwm2mConfigFormGroup.get('bootstrapServer').dirty + && this.lwm2mConfigFormGroup.get('bootstrapServer').valid) { + this.jsonAllConfig.bootstrap.bootstrapServer = bootstrap; + this.upDateJsonAllConfig(); + } + const serverConfig = this.lwm2mConfigFormGroup.get('lwm2mServer').value; + if (serverConfig !== null + && this.lwm2mConfigFormGroup.get('lwm2mServer').dirty + && this.lwm2mConfigFormGroup.get('lwm2mServer').valid) { + this.jsonAllConfig.bootstrap.lwm2mServer = serverConfig; + this.upDateJsonAllConfig(); + } + } + + private updateBootstrapSettings() { + const securityMode = 'securityMode'; + this.jsonAllConfig.client.identity = this.lwm2mConfigFormGroup.get('identity').value; + this.jsonAllConfig.client.key = this.lwm2mConfigFormGroup.get('key').value; + if (this.lwm2mConfigFormGroup.get('bootstrapServer').value[securityMode] === Lwm2mSecurityType.PSK) { + this.jsonAllConfig.bootstrap.bootstrapServer.clientPublicKeyOrId = this.jsonAllConfig.client.identity; + this.jsonAllConfig.bootstrap.bootstrapServer.clientSecretKey = this.jsonAllConfig.client.key; + this.lwm2mConfigFormGroup.get('bootstrapServer').patchValue(this.jsonAllConfig.bootstrap.bootstrapServer, {emitEvent: false}); + } + if (this.lwm2mConfigFormGroup.get('lwm2mServer').value[securityMode] === Lwm2mSecurityType.PSK) { + this.jsonAllConfig.bootstrap.lwm2mServer.clientPublicKeyOrId = this.jsonAllConfig.client.identity; + this.jsonAllConfig.bootstrap.lwm2mServer.clientSecretKey = this.jsonAllConfig.client.key; + this.lwm2mConfigFormGroup.get('lwm2mServer').patchValue(this.jsonAllConfig.bootstrap.lwm2mServer, {emitEvent: false}); + } + } + + private upDateJsonAllConfig = (): void => { + this.lwm2mConfigFormGroup.patchValue({ + jsonAllConfig: this.jsonAllConfig + }, {emitEvent: false}); + } + + private initLwm2mConfigFormGroup = (): FormGroup => { + if (this.jsonAllConfig.client.securityConfigClientMode === Lwm2mSecurityType.PSK) { + this.data.endPoint = this.jsonAllConfig.client.endpoint; + } + const formGroup = this.fb.group({ + securityConfigClientMode: [this.jsonAllConfig.client.securityConfigClientMode], + identity: [''], + key: [''], + x509: [false], + bootstrapServer: [this.jsonAllConfig.bootstrap.bootstrapServer], + lwm2mServer: [this.jsonAllConfig.bootstrap.lwm2mServer], + endPoint: [this.data.endPoint], + jsonAllConfig: [this.jsonAllConfig] + }); + formGroup.get('securityConfigClientMode').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((type) => { + this.securityConfigClientModeChanged(type); + }); + formGroup.get('endPoint').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe((endpoint) => { + if (formGroup.get('securityConfigClientMode').value === Lwm2mSecurityType.PSK) { + this.jsonAllConfig.client.endpoint = endpoint; + this.upDateJsonAllConfig(); + } + }); + return formGroup; + } + + save(): void { + this.upDateValueToJson(); + this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value.split('\'').join(''); + this.data.jsonAllConfig = this.jsonAllConfig; + if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === Lwm2mSecurityType.PSK) { + this.data.endPoint = this.data.jsonAllConfig.client.identity; + } + this.dialogRef.close(this.data); + } + + cancel(): void { + this.dialogRef.close(undefined); + } +} + + 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 f2208757d4..a5a3ed460b 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 @@ -138,6 +138,8 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb import { EdgeDownlinkTableComponent } from '@home/components/edge/edge-downlink-table.component'; import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component'; import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; +import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; +import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component'; @NgModule({ declarations: @@ -239,6 +241,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag DeviceWizardDialogComponent, DeviceCredentialsComponent, CopyDeviceCredentialsComponent, + SecurityConfigLwm2mComponent, + SecurityConfigLwm2mServerComponent, AlarmScheduleDialogComponent, EditAlarmDetailsDialogComponent, SmsProviderConfigurationComponent, @@ -342,6 +346,8 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag DeviceWizardDialogComponent, DeviceCredentialsComponent, CopyDeviceCredentialsComponent, + SecurityConfigLwm2mComponent, + SecurityConfigLwm2mServerComponent, AlarmScheduleInfoComponent, AlarmScheduleComponent, AlarmScheduleDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.ts index 834be7535c..c1877e218e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.ts @@ -14,21 +14,20 @@ /// limitations under the License. /// -import {Component, Inject, OnInit, SkipSelf} from "@angular/core"; -import {ErrorStateMatcher} from "@angular/material/core"; -import {DialogComponent} from "@shared/components/dialog.component"; -import {Store} from "@ngrx/store"; -import {AppState} from "@core/core.state"; -import {Router} from "@angular/router"; -import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; -import {FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm} from "@angular/forms"; -import {TranslateService} from "@ngx-translate/core"; -import {JsonObject} from "@angular/compiler-cli/ngcc/src/packages/entry_point"; +import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { ErrorStateMatcher } from '@angular/material/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; export interface Lwm2mAttributesDialogData { readonly: boolean; attributeLwm2m: JsonObject; - destName: string + destName: string; } @Component({ @@ -37,7 +36,8 @@ export interface Lwm2mAttributesDialogData { styleUrls: ['./lwm2m-attributes.component.scss'], providers: [{provide: ErrorStateMatcher, useExisting: Lwm2mAttributesDialogComponent}], }) -export class Lwm2mAttributesDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { +export class Lwm2mAttributesDialogComponent extends DialogComponent + implements OnInit, ErrorStateMatcher { readonly = this.data.readonly; @@ -54,8 +54,7 @@ export class Lwm2mAttributesDialogComponent extends DialogComponent, - private fb: FormBuilder, - public translate: TranslateService) { + private fb: FormBuilder) { super(store, router, dialogRef); this.attributeLwm2mDialogFormGroup = this.fb.group({ diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.ts index 7d7af196f0..686382e9bd 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import {Component, forwardRef, Input, OnInit} from "@angular/core"; +import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -26,17 +26,17 @@ import { NG_VALUE_ACCESSOR, Validator, Validators -} from "@angular/forms"; -import {Subscription} from "rxjs"; -import {PageComponent} from "@shared/components/page.component"; -import {Store} from "@ngrx/store"; -import {AppState} from "@core/core.state"; +} from '@angular/forms'; +import { Subscription } from 'rxjs'; import { ATTRIBUTE_KEYS, ATTRIBUTE_LWM2M_ENUM, ATTRIBUTE_LWM2M_MAP -} from "@home/components/profile/device/lwm2m/lwm2m-profile-config.models"; -import {isDefinedAndNotNull, isEmpty, isEmptyStr, isUndefinedOrNull} from "@core/utils"; +} from './lwm2m-profile-config.models'; +import { isDefinedAndNotNull, isEmpty, isEmptyStr, isUndefinedOrNull } from '@core/utils'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { PageComponent } from '@shared/components/page.component'; @Component({ @@ -98,7 +98,7 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co registerOnTouched(fn: any): void { } - setDisabledState?(isDisabled: boolean): void { + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { this.kvListFormGroup.disable({emitEvent: false}); @@ -166,10 +166,10 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co }; } - private updateValidate (c?: FormControl) { + private updateValidate() { const kvList = this.kvListFormGroup.get('keyVals') as FormArray; kvList.controls.forEach(fg => { - if (fg.get('key').value==='ver') { + if (fg.get('key').value === 'ver') { fg.get('value').setValidators(null); fg.get('value').setErrors(null); } @@ -189,7 +189,7 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co if (isUndefinedOrNull(entry.value) || entry.key === 'ver' || isEmptyStr(entry.value.toString())) { keyValMap[entry.key] = entry.value.toString(); } else { - keyValMap[entry.key] = Number(entry.value) + keyValMap[entry.key] = Number(entry.value); } }); this.propagateChange(keyValMap); @@ -215,13 +215,13 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co private attributeLwm2mValueNumberValidator = (control: AbstractControl) => { if (isNaN(Number(control.value)) || Number(control.value) < 0) { return { - 'validAttributeValue': true + validAttributeValue: true }; } return null; } - private attributeLwm2mValueValidator = (property: string): Object [] => { - return property === 'ver'? [] : [this.attributeLwm2mValueNumberValidator]; + private attributeLwm2mValueValidator = (property: string): object[] => { + return property === 'ver' ? [] : [this.attributeLwm2mValueNumberValidator]; } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.ts index ca4b66ae36..1e467bcd93 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.ts @@ -14,21 +14,14 @@ /// limitations under the License. /// -import {Component, EventEmitter, forwardRef, Inject, Input, Output} from "@angular/core"; -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from "@angular/forms"; -import {coerceBooleanProperty} from "@angular/cdk/coercion"; -import {Store} from "@ngrx/store"; -import {AppState} from "@core/core.state"; -import {DeviceProfileService} from "@core/http/device-profile.service"; -import {WINDOW} from "@core/services/window.service"; -import {deepClone, isDefinedAndNotNull, isEmpty} from "@core/utils"; -import { - Lwm2mAttributesDialogComponent, - Lwm2mAttributesDialogData -} from "@home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component"; -import {MatDialog} from "@angular/material/dialog"; -import {TranslateService} from "@ngx-translate/core"; -import {ATTRIBUTE_LWM2M_LABEL} from "@home/components/profile/device/lwm2m/lwm2m-profile-config.models"; +import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { deepClone, isDefinedAndNotNull, isEmpty } from '@core/utils'; +import { Lwm2mAttributesDialogComponent, Lwm2mAttributesDialogData } from './lwm2m-attributes-dialog.component'; +import { MatDialog } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; +import { ATTRIBUTE_LWM2M_LABEL } from './lwm2m-profile-config.models'; @Component({ @@ -46,7 +39,6 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor { attributeLwm2mLabel = ATTRIBUTE_LWM2M_LABEL; private requiredValue: boolean; - private dirty = false; @Input() attributeLwm2m: {}; @@ -71,12 +63,9 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor { private propagateChange = (v: any) => { } - constructor(private store: Store, - private dialog: MatDialog, + constructor(private dialog: MatDialog, private fb: FormBuilder, - private deviceProfileService: DeviceProfileService, - private translate: TranslateService, - @Inject(WINDOW) private window: Window) {} + private translate: TranslateService) {} registerOnChange(fn: any): void { this.propagateChange = fn; @@ -117,20 +106,20 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor { return label; } - isDisableBtn (): boolean { + isDisableBtn(): boolean { return this.disabled || this.isAttributeTelemetry ? !(isDefinedAndNotNull(this.attributeLwm2m) && !isEmpty(this.attributeLwm2m) && this.disabled) : this.disabled; } - isIconView (): boolean { + isIconView(): boolean { return this.isAttributeTelemetry || this.disabled; } - isIconEditAdd (): boolean { + isIconEditAdd(): boolean { return isDefinedAndNotNull(this.attributeLwm2m) && !isEmpty(this.attributeLwm2m); } - isToolTipLabel (): string { + isToolTipLabel(): string { return this.disabled ? this.translate.instant('device-profile.lwm2m.attribute-lwm2m-tip') : this.isAttributeTelemetry ? this.translate.instant('device-profile.lwm2m.attribute-lwm2m-disable-tip') : this.translate.instant('device-profile.lwm2m.attribute-lwm2m-tip'); @@ -140,7 +129,7 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor { if ($event) { $event.stopPropagation(); } - this.dialog.open(Lwm2mAttributesDialogComponent, { + this.dialog.open(Lwm2mAttributesDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts index b82da4bc06..55f8db8f84 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.ts @@ -14,8 +14,8 @@ /// limitations under the License. /// -import {Component, forwardRef, Inject, Input} from '@angular/core'; -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; +import { Component, forwardRef, Inject, Input } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; import { DEFAULT_CLIENT_HOLD_OFF_TIME, DEFAULT_ID_SERVER, @@ -28,13 +28,12 @@ import { SECURITY_CONFIG_MODE_NAMES, ServerSecurityConfig } from './lwm2m-profile-config.models'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; -import {WINDOW} from '@core/services/window.service'; -import {pairwise, startWith} from 'rxjs/operators'; -import {DeviceProfileService} from '@core/http/device-profile.service'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { WINDOW } from '@core/services/window.service'; +import { pairwise, startWith } from 'rxjs/operators'; +import { DeviceProfileService } from '@core/http/device-profile.service'; +// @dynamic @Component({ selector: 'tb-profile-lwm2m-device-config-server', templateUrl: './lwm2m-device-config-server.component.html', @@ -73,8 +72,7 @@ export class Lwm2mDeviceConfigServerComponent implements ControlValueAccessor { this.requiredValue = coerceBooleanProperty(value); } - constructor(protected store: Store, - public fb: FormBuilder, + constructor(public fb: FormBuilder, private deviceProfileService: DeviceProfileService, @Inject(WINDOW) private window: Window) { this.serverFormGroup = this.initServerGroup(); diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts index d6f6823db6..60fb27a752 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.ts @@ -15,10 +15,8 @@ /// import { DeviceProfileTransportConfiguration } from '@shared/models/device.models'; -import { Component, forwardRef, Inject, Input } from '@angular/core'; +import { Component, forwardRef, Input } 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 { ATTRIBUTE, @@ -37,7 +35,6 @@ import { } from './lwm2m-profile-config.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/utils'; -import { WINDOW } from '@core/services/window.service'; import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; import { Direction } from '@shared/models/page/sort-order'; @@ -78,10 +75,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private propagateChange = (v: any) => { } - constructor(private store: Store, - private fb: FormBuilder, - private deviceProfileService: DeviceProfileService, - @Inject(WINDOW) private window: Window) { + constructor(private fb: FormBuilder, + private deviceProfileService: DeviceProfileService) { this.lwm2mDeviceProfileFormGroup = this.fb.group({ clientOnlyObserveAfterConnect: [1, []], objectIds: [null, Validators.required], @@ -155,7 +150,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.lwm2mDeviceProfileFormGroup.patchValue({ clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, objectIds: value, - observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value['objectsList']), + observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value.objectsList), shortId: this.configurationValue.bootstrap.servers.shortId, lifetime: this.configurationValue.bootstrap.servers.lifetime, defaultMinPeriod: this.configurationValue.bootstrap.servers.defaultMinPeriod, @@ -171,7 +166,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro let configuration: DeviceProfileTransportConfiguration = null; if (this.lwm2mDeviceConfigFormGroup.valid && this.lwm2mDeviceProfileFormGroup.valid) { configuration = this.lwm2mDeviceConfigFormGroup.value.configurationJson; - // configuration.type = DeviceTransportType.LWM2M; } this.propagateChange(configuration); } @@ -206,7 +200,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro if (this.configurationValue.observeAttr && objectLwM2MS.length > 0) { const attributeArray = this.configurationValue.observeAttr.attribute; const telemetryArray = this.configurationValue.observeAttr.telemetry; - let keyNameJson = this.configurationValue.observeAttr.keyName; + const keyNameJson = this.configurationValue.observeAttr.keyName; if (this.includesNotZeroInstance(attributeArray, telemetryArray)) { this.addInstances(attributeArray, telemetryArray, objectLwM2MS); } @@ -270,9 +264,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private updateAttributeLwm2m = (objectLwM2MS: ObjectLwM2M[]): void => { Object.keys(this.configurationValue.observeAttr.attributeLwm2m).forEach(key => { const [objectKeyId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), String); - let objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId); + const objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId); if (objectLwM2M && instanceId) { - let instance = objectLwM2M.instances.find(instance => instance.id === +instanceId) + const instance = objectLwM2M.instances.find(obj => obj.id === +instanceId); if (instance && resourceId) { instance.resources.find(resource => resource.id === +resourceId) .attributeLwm2m = this.configurationValue.observeAttr.attributeLwm2m[key]; @@ -288,7 +282,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private updateKeyNameObjects = (objectLwM2MS: ObjectLwM2M[]): void => { Object.keys(this.configurationValue.observeAttr.keyName).forEach(key => { const [objectKeyId, instanceId, resourceId] = Array.from(key.substring(1).split('/'), String); - const objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId) + const objectLwM2M = objectLwM2MS.find(objectLwm2m => objectLwm2m.keyId === objectKeyId); if (objectLwM2M) { objectLwM2M.instances.find(instance => instance.id === +instanceId) .resources.find(resource => resource.id === +resourceId) @@ -299,7 +293,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private validateKeyNameObjects = (nameJson: JsonObject, attributeArray: JsonArray, telemetryArray: JsonArray): {} => { const keyName = JSON.parse(JSON.stringify(nameJson)); - let keyNameValidate = {}; + const keyNameValidate = {}; const keyAttrTelemetry = attributeArray.concat(telemetryArray); Object.keys(keyName).forEach(key => { if (keyAttrTelemetry.includes(key)) { @@ -345,9 +339,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro attributeLwm2m[pathRes] = resource.attributeLwm2m; } } - }) + }); } - }) + }); } }); if (isUndefined(this.configurationValue.observeAttr)) { @@ -356,7 +350,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro attribute: attributeArray, telemetry: telemetryArray, keyName: this.sortObjectKeyPathJson(KEY_NAME, keyNameNew), - attributeLwm2m: attributeLwm2m + attributeLwm2m }; } else { this.configurationValue.observeAttr.observe = observeArray; @@ -457,7 +451,6 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private removeAttributeLwm2mFromJson = (keyId: string): void => { - debugger const keyNameJson = this.configurationValue.observeAttr.attributeLwm2m; Object.keys(keyNameJson).forEach(key => { if (key.startsWith(`/${keyId}`)) { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.ts index c480356e72..030b19b6d2 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component.ts @@ -14,13 +14,13 @@ /// limitations under the License. /// -import {Component, Inject, OnInit} from '@angular/core'; -import {DialogComponent} from '@shared/components/dialog.component'; -import {FormBuilder, FormGroup} from '@angular/forms'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {Router} from '@angular/router'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; +import { Component, Inject, OnInit } from '@angular/core'; +import { DialogComponent } from '@shared/components/dialog.component'; +import { FormBuilder, FormGroup } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { Router } from '@angular/router'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; export interface Lwm2mObjectAddInstancesData { instancesIds: Set; diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts index cda075a142..29f5bb33cf 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-add-instances-list.component.ts @@ -14,11 +14,9 @@ /// limitations under the License. /// -import {Component, forwardRef} from '@angular/core'; -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER} from './lwm2m-profile-config.models'; +import { Component, forwardRef } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { INSTANCES_ID_VALUE_MAX, INSTANCES_ID_VALUE_MIN, KEY_REGEXP_NUMBER } from './lwm2m-profile-config.models'; @Component({ selector: 'tb-profile-lwm2m-object-add-instances-list', @@ -41,8 +39,7 @@ export class Lwm2mObjectAddInstancesListComponent implements ControlValueAccesso private propagateChange = (v: any) => { }; - constructor(private store: Store, - private fb: FormBuilder) { + constructor(private fb: FormBuilder) { this.instancesListFormGroup = this.fb.group({ instanceIdInput: [null, [ Validators.min(this.instanceIdValueMin), diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts index ba54f63411..8f1f676769 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.ts @@ -14,18 +14,16 @@ /// limitations under the License. /// -import {Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild} from '@angular/core'; -import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {Observable} from 'rxjs'; -import {filter, map, mergeMap, publishReplay, refCount, tap} from 'rxjs/operators'; -import {ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT} from './lwm2m-profile-config.models'; -import {DeviceProfileService} from '@core/http/device-profile.service'; -import {Direction} from '@shared/models/page/sort-order'; -import {isDefined, isDefinedAndNotNull, isString} from '@core/utils'; -import {PageLink} from "@shared/models/page/page-link"; +import { Component, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { Observable } from 'rxjs'; +import { filter, map, mergeMap, publishReplay, refCount, tap } from 'rxjs/operators'; +import { ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT } from './lwm2m-profile-config.models'; +import { DeviceProfileService } from '@core/http/device-profile.service'; +import { Direction } from '@shared/models/page/sort-order'; +import { isDefined, isDefinedAndNotNull, isString } from '@core/utils'; +import { PageLink } from '@shared/models/page/page-link'; @Component({ selector: 'tb-profile-lwm2m-object-list', @@ -71,8 +69,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V private propagateChange = (v: any) => { } - constructor(private store: Store, - private deviceProfileService: DeviceProfileService, + constructor(private deviceProfileService: DeviceProfileService, private fb: FormBuilder) { this.lwm2mListFormGroup = this.fb.group({ objectsList: [this.objectsList], @@ -162,20 +159,20 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V private fetchListObjects = (searchText?: string): Observable> => { this.searchText = searchText; - return this.getLwM2mModelsPage().pipe( - map(objectLwM2Ms => objectLwM2Ms) - ); + return this.getLwM2mModelsPage().pipe( + map(objectLwM2Ms => objectLwM2Ms) + ); } private getLwM2mModelsPage(): Observable> { - const pageLink = new PageLink(PAGE_SIZE_LIMIT, 0, this.searchText, { - property: 'id', - direction: Direction.ASC - }); - this.lw2mModels = this.deviceProfileService.getLwm2mObjectsPage(pageLink).pipe( - publishReplay(1), - refCount() - ); + const pageLink = new PageLink(PAGE_SIZE_LIMIT, 0, this.searchText, { + property: 'id', + direction: Direction.ASC + }); + this.lw2mModels = this.deviceProfileService.getLwm2mObjectsPage(pageLink).pipe( + publishReplay(1), + refCount() + ); return this.lw2mModels; } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts index c824b000a8..c3003c7c3e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.ts @@ -14,13 +14,11 @@ /// limitations under the License. /// -import {Component, forwardRef, Input} from '@angular/core'; -import {ControlValueAccessor, FormArray, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators} from '@angular/forms'; -import {ResourceLwM2M, RESOURCES} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; +import { Component, forwardRef, Input } from '@angular/core'; +import { ControlValueAccessor, FormArray, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { ResourceLwM2M, RESOURCES } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; import _ from 'lodash'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; @Component({ selector: 'tb-profile-lwm2m-observe-attr-telemetry-resource', @@ -54,8 +52,7 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA } } - constructor(private store: Store, - private fb: FormBuilder) { + constructor(private fb: FormBuilder) { this.resourceFormGroup = this.fb.group({ resources: this.fb.array([]) }); @@ -95,7 +92,7 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA } getNameResourceLwm2m = (resourceLwM2M: ResourceLwM2M): string => { - return '<' + resourceLwM2M.id +'> ' + resourceLwM2M.name; + return `<${resourceLwM2M.id}> ${resourceLwM2M.name}`; } createResourceLwM2M(resourcesLwM2M: ResourceLwM2M[]): void { @@ -135,14 +132,14 @@ export class Lwm2mObserveAttrTelemetryResourceComponent implements ControlValueA return index; } - updateObserve = (index: number): void =>{ + updateObserve = (index: number): void => { if (this.resourceFormArray.at(index).value.attribute === false && this.resourceFormArray.at(index).value.telemetry === false) { this.resourceFormArray.at(index).patchValue({observe: false}); this.resourceFormArray.at(index).patchValue({attributeLwm2m: {}}); } } - disableObserve = (index: number): boolean =>{ + disableObserve = (index: number): boolean => { return !this.resourceFormArray.at(index).value.telemetry && !this.resourceFormArray.at(index).value.attribute; } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts index c38c413e98..77a5956855 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import {Component, forwardRef, Input} from '@angular/core'; +import { Component, forwardRef, Input } from '@angular/core'; import { AbstractControl, ControlValueAccessor, @@ -24,9 +24,7 @@ import { NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {coerceBooleanProperty} from '@angular/cdk/coercion'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ATTRIBUTE, ATTRIBUTE_LWM2M, @@ -38,9 +36,8 @@ import { RESOURCES, TELEMETRY } from './lwm2m-profile-config.models'; -import {deepClone, isDefinedAndNotNull, isEqual, isUndefined} from '@core/utils'; -import {MatDialog} from '@angular/material/dialog'; -import {TranslateService} from '@ngx-translate/core'; +import { deepClone, isDefinedAndNotNull, isEqual, isUndefined } from '@core/utils'; +import { MatDialog } from '@angular/material/dialog'; import { Lwm2mObjectAddInstancesData, Lwm2mObjectAddInstancesDialogComponent @@ -83,10 +80,8 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor @Input() disabled: boolean; - constructor(private store: Store, - private fb: FormBuilder, - private dialog: MatDialog, - public translate: TranslateService) { + constructor(private fb: FormBuilder, + private dialog: MatDialog) { this.observeAttrTelemetryFormGroup = this.fb.group({ [CLIENT_LWM2M]: this.fb.array([]) }); @@ -98,7 +93,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor } private propagateChange = (v: any) => { - }; + } registerOnChange(fn: any): void { this.propagateChange = fn; @@ -189,7 +184,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M).updateValueAndValidity(); } - trackByParams = (index: number, element: any): number => { + trackByParams = (index: number): number => { return index; } @@ -252,7 +247,6 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor } private updateInstancesIds = (data: Lwm2mObjectAddInstancesData): void => { - debugger const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls .find(e => e.value.keyId === data.objectKeyId) as FormGroup; const instancesArray = objectLwM2MFormGroup.value.instances as Instance []; @@ -318,7 +312,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor return objectName + ' <' + idVerObj + '>'; } getNameInstanceLwm2m = (instance: Instance, idVerObj: string): string => { - return ' instance <' + idVerObj + '/' + instance.id +'>'; + return ` instance <${idVerObj}/${instance.id}>`; } updateAttributeLwm2mObject = (event: Event, objectKeyId: number): void => { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts index 43c297df56..4575d2bfff 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-components.module.ts @@ -14,19 +14,19 @@ /// limitations under the License. /// -import {NgModule} from '@angular/core'; -import {Lwm2mDeviceProfileTransportConfigurationComponent} from './lwm2m-device-profile-transport-configuration.component'; -import {Lwm2mObjectListComponent} from './lwm2m-object-list.component'; -import {Lwm2mObserveAttrTelemetryComponent} from './lwm2m-observe-attr-telemetry.component'; -import {Lwm2mObserveAttrTelemetryResourceComponent} from './lwm2m-observe-attr-telemetry-resource.component'; -import {Lwm2mAttributesDialogComponent} from './lwm2m-attributes-dialog.component'; -import {Lwm2mAttributesComponent} from './lwm2m-attributes.component'; -import {Lwm2mAttributesKeyListComponent} from './lwm2m-attributes-key-list.component'; -import {Lwm2mDeviceConfigServerComponent} from './lwm2m-device-config-server.component'; -import {Lwm2mObjectAddInstancesDialogComponent} from './lwm2m-object-add-instances-dialog.component'; -import {Lwm2mObjectAddInstancesListComponent} from './lwm2m-object-add-instances-list.component'; -import {CommonModule} from '@angular/common'; -import {SharedModule} from '@app/shared/shared.module'; +import { NgModule } from '@angular/core'; +import { Lwm2mDeviceProfileTransportConfigurationComponent } from './lwm2m-device-profile-transport-configuration.component'; +import { Lwm2mObjectListComponent } from './lwm2m-object-list.component'; +import { Lwm2mObserveAttrTelemetryComponent } from './lwm2m-observe-attr-telemetry.component'; +import { Lwm2mObserveAttrTelemetryResourceComponent } from './lwm2m-observe-attr-telemetry-resource.component'; +import { Lwm2mAttributesDialogComponent } from './lwm2m-attributes-dialog.component'; +import { Lwm2mAttributesComponent } from './lwm2m-attributes.component'; +import { Lwm2mAttributesKeyListComponent } from './lwm2m-attributes-key-list.component'; +import { Lwm2mDeviceConfigServerComponent } from './lwm2m-device-config-server.component'; +import { Lwm2mObjectAddInstancesDialogComponent } from './lwm2m-object-add-instances-dialog.component'; +import { Lwm2mObjectAddInstancesListComponent } from './lwm2m-object-add-instances-list.component'; +import { CommonModule } from '@angular/common'; +import { SharedModule } from '@app/shared/shared.module'; @NgModule({ declarations: diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts index ff927bc09f..f6e58df903 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-profile-config.models.ts @@ -127,8 +127,8 @@ export const SECURITY_CONFIG_MODE_NAMES = new Map( ); export interface ModelValue { - objectIds: string[], - objectsList: ObjectLwM2M[] + objectIds: string[]; + objectsList: ObjectLwM2M[]; } export interface BootstrapServersSecurityConfig { diff --git a/ui-ngx/src/app/modules/home/pages/device/device.module.ts b/ui-ngx/src/app/modules/home/pages/device/device.module.ts index f9ece52b22..8aeda0e2c1 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device.module.ts +++ b/ui-ngx/src/app/modules/home/pages/device/device.module.ts @@ -24,8 +24,6 @@ import { DeviceCredentialsDialogComponent } from '@modules/home/pages/device/dev import { HomeDialogsModule } from '../../dialogs/home-dialogs.module'; import { HomeComponentsModule } from '@modules/home/components/home-components.module'; import { DeviceTabsComponent } from '@home/pages/device/device-tabs.component'; -import { SecurityConfigComponent } from '@home/pages/device/lwm2m/security-config.component'; -import { SecurityConfigServerComponent } from '@home/pages/device/lwm2m/security-config-server.component'; import { DefaultDeviceConfigurationComponent } from './data/default-device-configuration.component'; import { DeviceConfigurationComponent } from './data/device-configuration.component'; import { DeviceDataComponent } from './data/device-data.component'; @@ -50,9 +48,7 @@ import { SnmpDeviceTransportConfigurationComponent } from './data/snmp-device-tr DeviceComponent, DeviceTabsComponent, DeviceTableHeaderComponent, - DeviceCredentialsDialogComponent, - SecurityConfigComponent, - SecurityConfigServerComponent + DeviceCredentialsDialogComponent ], imports: [ CommonModule, diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html deleted file mode 100644 index bd02c9dee3..0000000000 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.html +++ /dev/null @@ -1,101 +0,0 @@ - -
-
-
-
- - device.lwm2m-security-config.mode - - - {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityMode]) }} - - - -
-
-
-
- - {{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }} - - {{clientPublicKeyOrId.value?.length || 0}}/{{lenMaxClientPublicKeyOrId}} - - {{ 'device.lwm2m-security-config.client-publicKey-or-id' | translate }} - {{ 'device.lwm2m-security-config.required' | translate }} - - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { - count: 0 - } }} - - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { - count: lenMaxClientPublicKeyOrId - } }} - - - - {{ 'device.lwm2m-security-config.client-secret-key' | translate }} - - {{clientSecretKey.value?.length || 0}}/{{lenMaxClientSecretKey}} - - {{ 'device.lwm2m-security-config.client-secret-key' | translate }} - {{ 'device.lwm2m-security-config.required' | translate }} - - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { - count: 0 - } }} - - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { - count: lenMaxClientSecretKey - } }} - - -
-
-
-
diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts deleted file mode 100644 index a068bb589e..0000000000 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config-server.component.ts +++ /dev/null @@ -1,142 +0,0 @@ -/// -/// Copyright © 2016-2021 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, Inject, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; -import { - DeviceCredentialsDialogLwm2mData, - KEY_REGEXP_HEX_DEC, - LEN_MAX_PRIVATE_KEY, - LEN_MAX_PSK, - LEN_MAX_PUBLIC_KEY_RPK, - LEN_MAX_PUBLIC_KEY_X509, - SECURITY_CONFIG_MODE, - SECURITY_CONFIG_MODE_NAMES, - ServerSecurityConfig -} from '@home/pages/device/lwm2m/security-config.models'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; -import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; -import { PageComponent } from '@shared/components/page.component'; - -@Component({ - selector: 'tb-security-config-server-lwm2m', - templateUrl: './security-config-server.component.html', - styleUrls: [], - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => SecurityConfigServerComponent), - multi: true - } - ] -}) - -export class SecurityConfigServerComponent extends PageComponent implements OnInit, ControlValueAccessor { - - securityConfigLwM2MType = SECURITY_CONFIG_MODE; - securityConfigLwM2MTypes = Object.keys(SECURITY_CONFIG_MODE); - credentialTypeLwM2MNamesMap = SECURITY_CONFIG_MODE_NAMES; - lenMinClientPublicKeyOrId = 0; - lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; - lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY; - lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; - - @Input() serverFormGroup: FormGroup; - - constructor(protected store: Store, - @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, - public dialogRef: MatDialogRef, - public fb: FormBuilder) { - super(store); - } - - ngOnInit(): void { - this.registerDisableOnLoadFormControl(this.serverFormGroup.get('securityMode')); - } - - private updateValueFields(serverData: ServerSecurityConfig): void { - this.serverFormGroup.patchValue(serverData, {emitEvent: false}); - const securityMode = this.serverFormGroup.get('securityMode').value as SECURITY_CONFIG_MODE; - this.updateValidate(securityMode); - } - - private updateValidate(securityMode: SECURITY_CONFIG_MODE): void { - switch (securityMode) { - case SECURITY_CONFIG_MODE.NO_SEC: - this.serverFormGroup.get('clientPublicKeyOrId').setValidators([]); - this.serverFormGroup.get('clientSecretKey').setValidators([]); - break; - case SECURITY_CONFIG_MODE.PSK: - this.lenMinClientPublicKeyOrId = 0; - this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; - this.lenMinClientSecretKey = LEN_MAX_PSK; - this.lenMaxClientSecretKey = LEN_MAX_PSK; - this.setValidatorsSecurity(securityMode); - break; - case SECURITY_CONFIG_MODE.RPK: - this.lenMinClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; - this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_RPK; - this.lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY; - this.lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; - this.setValidatorsSecurity(securityMode); - break; - case SECURITY_CONFIG_MODE.X509: - this.lenMinClientPublicKeyOrId = 0; - this.lenMaxClientPublicKeyOrId = LEN_MAX_PUBLIC_KEY_X509; - this.lenMinClientSecretKey = LEN_MAX_PRIVATE_KEY; - this.lenMaxClientSecretKey = LEN_MAX_PRIVATE_KEY; - this.setValidatorsSecurity(securityMode); - break; - } - this.serverFormGroup.updateValueAndValidity(); - } - - private setValidatorsSecurity = (securityMode: SECURITY_CONFIG_MODE): void => { - if (securityMode === SECURITY_CONFIG_MODE.PSK) { - this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required]); - } else { - this.serverFormGroup.get('clientPublicKeyOrId').setValidators([Validators.required, - Validators.pattern(KEY_REGEXP_HEX_DEC), - Validators.minLength(this.lenMinClientPublicKeyOrId), - Validators.maxLength(this.lenMaxClientPublicKeyOrId)]); - } - - this.serverFormGroup.get('clientSecretKey').setValidators([Validators.required, - Validators.pattern(KEY_REGEXP_HEX_DEC), - Validators.minLength(this.lenMinClientSecretKey), - Validators.maxLength(this.lenMaxClientSecretKey)]); - } - - securityModeChanged(securityMode: SECURITY_CONFIG_MODE): void { - this.updateValidate(securityMode); - } - - writeValue(value: any): void { - if (value) { - this.updateValueFields(value); - } - } - - registerOnChange(fn: (value: any) => any): void { - } - - registerOnTouched(fn: any): void { - } - - setDisabledState?(isDisabled: boolean): void { - } -} diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html deleted file mode 100644 index db0946b371..0000000000 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.html +++ /dev/null @@ -1,180 +0,0 @@ - -
- -

{{ title }}

- - -
-
-
- - device.lwm2m-security-config.endpoint - - - {{ 'device.lwm2m-security-config.endpoint' | translate }} - {{ 'device.lwm2m-security-config.required' | translate }} - - - - - -
- - device.lwm2m-security-config.mode - - - {{ credentialTypeLwM2MNamesMap.get(securityConfigLwM2MType[securityConfigClientMode]) }} - - - -
- - {{ 'device.lwm2m-security-config.identity' | translate }} - - - {{ 'device.lwm2m-security-config.identity' | translate }} - {{ 'device.lwm2m-security-config.required' | translate }} - - -
-
- - {{ 'device.lwm2m-security-config.client-key' | translate }} - - {{clientKey.value?.length || 0}}/{{lenMaxKeyClient}} - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.required' | translate }} - - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { - count: 0 - } }} - - - {{ 'device.lwm2m-security-config.client-key' | translate }} - {{ 'device.lwm2m-security-config.pattern_hex_dec' | translate: { - count: lenMaxKeyClient - } }} - - -
-
- - {{ 'device.lwm2m-security-config.client-certificate' | translate }} - -
-
-
-
- - -
- - - - -
{{ 'device.lwm2m-security-config.bootstrap-server' | translate | uppercase }}
-
-
- -
- - -
-
-
-
- - - - -
{{ 'device.lwm2m-security-config.lwm2m-server' | translate | uppercase }}
-
-
- -
- - -
-
-
-
-
-
-
- - -
-
- - -
-
-
-
-
-
-
-
- - - -
-
diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.ts b/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.ts deleted file mode 100644 index 028f28c72a..0000000000 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.component.ts +++ /dev/null @@ -1,382 +0,0 @@ -/// -/// Copyright © 2016-2021 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 {DialogComponent} from '@shared/components/dialog.component'; -import {Store} from '@ngrx/store'; -import {AppState} from '@core/core.state'; -import {Router} from '@angular/router'; -import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog'; -import {FormBuilder, FormGroup, Validators} from '@angular/forms'; -import {TranslateService} from '@ngx-translate/core'; -import { - BOOTSTRAP_SERVER, - BOOTSTRAP_SERVERS, - ClientSecurityConfig, - DeviceCredentialsDialogLwm2mData, - getClientSecurityConfig, - JSON_ALL_CONFIG, - KEY_REGEXP_HEX_DEC, - LEN_MAX_PSK, - LEN_MAX_PUBLIC_KEY_RPK, - LWM2M_SERVER, - SECURITY_CONFIG_MODE, - SECURITY_CONFIG_MODE_NAMES, - SecurityConfigModels -} from './security-config.models'; -import {WINDOW} from '@core/services/window.service'; -import {MatTabChangeEvent} from '@angular/material/tabs'; -import {MatTab} from '@angular/material/tabs/tab'; - -@Component({ - selector: 'tb-security-config-lwm2m', - templateUrl: './security-config.component.html', - styleUrls: [] -}) - -export class SecurityConfigComponent extends DialogComponent implements OnInit { - - lwm2mConfigFormGroup: FormGroup; - title: string; - submitted = false; - securityConfigLwM2MType = SECURITY_CONFIG_MODE; - securityConfigLwM2MTypes = Object.keys(SECURITY_CONFIG_MODE); - credentialTypeLwM2MNamesMap = SECURITY_CONFIG_MODE_NAMES; - formControlNameJsonAllConfig = JSON_ALL_CONFIG; - jsonAllConfig: SecurityConfigModels; - bootstrapServers: string; - bootstrapServer: string; - lwm2mServer: string; - jsonObserveData: {}; - lenMaxKeyClient = LEN_MAX_PSK; - tabPrevious: MatTab; - tabIndexPrevious = 0; - - constructor(protected store: Store, - protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: DeviceCredentialsDialogLwm2mData, - public dialogRef: MatDialogRef, - public fb: FormBuilder, - public translate: TranslateService, - @Inject(WINDOW) private window: Window) { - super(store, router, dialogRef); - } - - ngOnInit(): void { - this.jsonAllConfig = JSON.parse(JSON.stringify(this.data.jsonAllConfig)) as SecurityConfigModels; - this.initConstants(); - this.lwm2mConfigFormGroup = this.initLwm2mConfigFormGroup(); - this.title = this.translate.instant('device.lwm2m-security-info') + ': ' + this.data.endPoint; - this.lwm2mConfigFormGroup.get('clientCertificate').disable(); - this.initClientSecurityConfig(this.lwm2mConfigFormGroup.get('jsonAllConfig').value); - this.registerDisableOnLoadFormControl(this.lwm2mConfigFormGroup.get('securityConfigClientMode')); - } - - private initConstants = (): void => { - this.bootstrapServers = BOOTSTRAP_SERVERS; - this.bootstrapServer = BOOTSTRAP_SERVER; - this.lwm2mServer = LWM2M_SERVER; - } - - /** - * initChildesFormGroup - */ - get bootstrapFormGroup(): FormGroup { - return this.lwm2mConfigFormGroup.get('bootstrapFormGroup') as FormGroup; - } - - get lwm2mServerFormGroup(): FormGroup { - return this.lwm2mConfigFormGroup.get('lwm2mServerFormGroup') as FormGroup; - } - - get observeAttrFormGroup(): FormGroup { - return this.lwm2mConfigFormGroup.get('observeFormGroup') as FormGroup; - } - - private initClientSecurityConfig = (jsonAllConfig: SecurityConfigModels): void => { - switch (jsonAllConfig.client.securityConfigClientMode) { - case SECURITY_CONFIG_MODE.NO_SEC: - break; - case SECURITY_CONFIG_MODE.PSK: - const clientSecurityConfigPSK = jsonAllConfig.client as ClientSecurityConfig; - this.lwm2mConfigFormGroup.patchValue({ - identityPSK: clientSecurityConfigPSK.identity, - clientKey: clientSecurityConfigPSK.key, - }, {emitEvent: false}); - break; - case SECURITY_CONFIG_MODE.RPK: - const clientSecurityConfigRPK = jsonAllConfig.client as ClientSecurityConfig; - this.lwm2mConfigFormGroup.patchValue({ - clientKey: clientSecurityConfigRPK.key, - }, {emitEvent: false}); - break; - case SECURITY_CONFIG_MODE.X509: - const clientSecurityConfigX509 = jsonAllConfig.client as ClientSecurityConfig; - this.lwm2mConfigFormGroup.patchValue({ - clientCertificate: clientSecurityConfigX509.x509 - }, {emitEvent: false}); - break; - } - this.securityConfigClientUpdateValidators(this.lwm2mConfigFormGroup.get('securityConfigClientMode').value); - } - - securityConfigClientModeChanged = (mode: SECURITY_CONFIG_MODE): void => { - switch (mode) { - case SECURITY_CONFIG_MODE.NO_SEC: - const clientSecurityConfigNoSEC = getClientSecurityConfig(mode) as ClientSecurityConfig; - this.jsonAllConfig.client = clientSecurityConfigNoSEC; - this.lwm2mConfigFormGroup.patchValue({ - jsonAllConfig: this.jsonAllConfig, - clientCertificate: false - }, {emitEvent: false}); - break; - case SECURITY_CONFIG_MODE.PSK: - const clientSecurityConfigPSK = getClientSecurityConfig(mode, this.lwm2mConfigFormGroup.get('endPoint') - .value) as ClientSecurityConfig; - clientSecurityConfigPSK.identity = this.data.endPoint; - clientSecurityConfigPSK.key = this.lwm2mConfigFormGroup.get('clientKey').value; - this.jsonAllConfig.client = clientSecurityConfigPSK; - this.lwm2mConfigFormGroup.patchValue({ - identityPSK: clientSecurityConfigPSK.identity, - clientCertificate: false - }, {emitEvent: false}); - break; - case SECURITY_CONFIG_MODE.RPK: - const clientSecurityConfigRPK = getClientSecurityConfig(mode) as ClientSecurityConfig; - clientSecurityConfigRPK.key = this.lwm2mConfigFormGroup.get('clientKey').value; - this.jsonAllConfig.client = clientSecurityConfigRPK; - this.lwm2mConfigFormGroup.patchValue({ - clientCertificate: false - }, {emitEvent: false}); - break; - case SECURITY_CONFIG_MODE.X509: - this.jsonAllConfig.client = getClientSecurityConfig(mode) as ClientSecurityConfig; - this.lwm2mConfigFormGroup.patchValue({ - clientCertificate: true - }, {emitEvent: false}); - break; - } - this.securityConfigClientUpdateValidators(mode); - } - - private securityConfigClientUpdateValidators = (mode: SECURITY_CONFIG_MODE): void => { - switch (mode) { - case SECURITY_CONFIG_MODE.NO_SEC: - this.setValidatorsNoSecX509(); - break; - case SECURITY_CONFIG_MODE.PSK: - this.lenMaxKeyClient = LEN_MAX_PSK; - this.setValidatorsPskRpk(mode); - break; - case SECURITY_CONFIG_MODE.RPK: - this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; - this.setValidatorsPskRpk(mode); - break; - case SECURITY_CONFIG_MODE.X509: - this.lenMaxKeyClient = LEN_MAX_PUBLIC_KEY_RPK; - this.setValidatorsNoSecX509(); - break; - } - this.lwm2mConfigFormGroup.updateValueAndValidity(); - } - - private setValidatorsNoSecX509 = (): void => { - this.lwm2mConfigFormGroup.get('identityPSK').setValidators([]); - this.lwm2mConfigFormGroup.get('clientKey').setValidators([]); - } - - private setValidatorsPskRpk = (mode: SECURITY_CONFIG_MODE): void => { - if (mode === SECURITY_CONFIG_MODE.PSK) { - this.lwm2mConfigFormGroup.get('identityPSK').setValidators([Validators.required]); - } else { - this.lwm2mConfigFormGroup.get('identityPSK').setValidators([]); - } - this.lwm2mConfigFormGroup.get('clientKey').setValidators([Validators.required, - Validators.pattern(KEY_REGEXP_HEX_DEC), - Validators.maxLength(this.lenMaxKeyClient), Validators.minLength(this.lenMaxKeyClient)]); - } - - tabChanged = (tabChangeEvent: MatTabChangeEvent): void => { - if (this.tabIndexPrevious !== tabChangeEvent.index) { this.upDateValueToJson(); } - this.tabIndexPrevious = tabChangeEvent.index; - } - - private upDateValueToJson(): void { - switch (this.tabIndexPrevious) { - case 0: - this.upDateValueToJsonTab0(); - break; - case 1: - this.upDateValueToJsonTab1(); - break; - case 2: - this.upDateValueToJsonTab2(); - break; - } - } - - private upDateValueToJsonTab0 = (): void => { - if (this.lwm2mConfigFormGroup !== null) { - if (!this.lwm2mConfigFormGroup.get('endPoint').pristine && this.lwm2mConfigFormGroup.get('endPoint').valid) { - this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value; - /** Client mode == PSK */ - if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === SECURITY_CONFIG_MODE.PSK) { - const endPoint = 'endpoint'; - this.jsonAllConfig.client[endPoint] = this.data.endPoint; - this.lwm2mConfigFormGroup.get('endPoint').markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - } - /** only Client mode == PSK */ - if (!this.lwm2mConfigFormGroup.get('identityPSK').pristine && this.lwm2mConfigFormGroup.get('identityPSK').valid) { - this.lwm2mConfigFormGroup.get('identityPSK').markAsPristine({ - onlySelf: true - }); - this.updateIdentityPSK(); - } - /** only Client mode == PSK (len = 64) || RPK (len = 182) */ - if (!this.lwm2mConfigFormGroup.get('clientKey').pristine && this.lwm2mConfigFormGroup.get('clientKey').valid) { - this.lwm2mConfigFormGroup.get('clientKey').markAsPristine({ - onlySelf: true - }); - this.updateClientKey(); - } - } - } - - private upDateValueToJsonTab1 = (): void => { - if (this.lwm2mConfigFormGroup !== null) { - if (this.bootstrapFormGroup !== null && !this.bootstrapFormGroup.pristine && this.bootstrapFormGroup.valid) { - this.jsonAllConfig.bootstrap.bootstrapServer = this.bootstrapFormGroup.value; - this.bootstrapFormGroup.markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - - if (this.lwm2mServerFormGroup !== null && !this.lwm2mServerFormGroup.pristine && this.lwm2mServerFormGroup.valid) { - this.jsonAllConfig.bootstrap.lwm2mServer = this.lwm2mServerFormGroup.value; - this.lwm2mServerFormGroup.markAsPristine({ - onlySelf: true - }); - this.upDateJsonAllConfig(); - } - } - } - - private upDateValueToJsonTab2 = (): void => { - if (!this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).pristine && - this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).valid) { - this.jsonAllConfig = this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).value; - this.lwm2mConfigFormGroup.get(this.formControlNameJsonAllConfig).markAsPristine({ - onlySelf: true - }); - } - } - - private updateIdentityPSK = (): void => { - const securityMode = 'securityMode'; - if (this.lwm2mConfigFormGroup.get('bootstrapServer').value[securityMode] === SECURITY_CONFIG_MODE.PSK) { - this.lwm2mConfigFormGroup.get('bootstrapFormGroup').patchValue({ - clientPublicKeyOrId: this.lwm2mConfigFormGroup.get('identityPSK').value - }); - const identity = 'identity'; - this.jsonAllConfig.client[identity] = this.lwm2mConfigFormGroup.get('identityPSK').value; - this.upDateJsonAllConfig(); - } - if (this.lwm2mConfigFormGroup.get('lwm2mServer').value[securityMode] === SECURITY_CONFIG_MODE.PSK) { - this.lwm2mConfigFormGroup.get('lwm2mServerFormGroup').patchValue({ - clientPublicKeyOrId: this.lwm2mConfigFormGroup.get('identityPSK').value - }); - this.jsonAllConfig.bootstrap.lwm2mServer.clientPublicKeyOrId = this.lwm2mConfigFormGroup.get('identityPSK').value; - this.upDateJsonAllConfig(); - } - } - - private updateClientKey = (): void => { - const key = 'key'; - const securityMode = 'securityMode'; - this.jsonAllConfig.client[key] = this.lwm2mConfigFormGroup.get('clientKey').value; - if (this.lwm2mConfigFormGroup.get('bootstrapServer').value[securityMode] === SECURITY_CONFIG_MODE.PSK) { - this.lwm2mConfigFormGroup.get('bootstrapServer').patchValue({ - clientSecretKey: this.jsonAllConfig.client[key] - }, {emitEvent: false}); - this.jsonAllConfig.bootstrap.bootstrapServer.clientSecretKey = this.jsonAllConfig.client[key]; - } - if (this.lwm2mConfigFormGroup.get('lwm2mServer').value[securityMode] === SECURITY_CONFIG_MODE.PSK) { - this.lwm2mConfigFormGroup.get('lwm2mServer').patchValue({ - clientSecretKey: this.jsonAllConfig.client[key] - }, {emitEvent: false}); - this.jsonAllConfig.bootstrap.lwm2mServer.clientSecretKey = this.jsonAllConfig.client[key]; - } - this.upDateJsonAllConfig(); - } - - private upDateJsonAllConfig = (): void => { - this.data.jsonAllConfig = JSON.parse(JSON.stringify(this.jsonAllConfig)); - this.lwm2mConfigFormGroup.patchValue({ - jsonAllConfig: JSON.parse(JSON.stringify(this.jsonAllConfig)) - }, {emitEvent: false}); - this.lwm2mConfigFormGroup.markAsDirty(); - } - - private initLwm2mConfigFormGroup = (): FormGroup => { - if (SECURITY_CONFIG_MODE[this.jsonAllConfig.client.securityConfigClientMode.toString()] === SECURITY_CONFIG_MODE.PSK) { - const endpoint = 'endpoint'; - this.data.endPoint = this.jsonAllConfig.client[endpoint]; - } - return this.fb.group({ - securityConfigClientMode: [SECURITY_CONFIG_MODE[this.jsonAllConfig.client.securityConfigClientMode.toString()], []], - identityPSK: ['', []], - clientKey: ['', []], - clientCertificate: [false, []], - bootstrapServer: [this.jsonAllConfig.bootstrap[this.bootstrapServer], []], - lwm2mServer: [this.jsonAllConfig.bootstrap[this.lwm2mServer], []], - bootstrapFormGroup: this.getServerGroup(), - lwm2mServerFormGroup: this.getServerGroup(), - endPoint: [this.data.endPoint, []], - jsonAllConfig: [this.jsonAllConfig, []] - }); - } - - private getServerGroup = (): FormGroup => { - return this.fb.group({ - securityMode: [this.fb.control(SECURITY_CONFIG_MODE.NO_SEC), []], - clientPublicKeyOrId: ['', []], - clientSecretKey: ['', []] - }); - } - - save(): void { - this.upDateValueToJson(); - this.data.endPoint = this.lwm2mConfigFormGroup.get('endPoint').value.split('\'').join(''); - this.data.jsonAllConfig = this.jsonAllConfig; - if (this.lwm2mConfigFormGroup.get('securityConfigClientMode').value === SECURITY_CONFIG_MODE.PSK) { - const identity = 'identity'; - this.data.endPoint = this.data.jsonAllConfig.client[identity]; - } - this.dialogRef.close(this.data); - } - - cancel(): void { - this.dialogRef.close(undefined); - } -} - - diff --git a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.models.ts b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts similarity index 60% rename from ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.models.ts rename to ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts index 32b8026a4c..de0f2dc8a5 100644 --- a/ui-ngx/src/app/modules/home/pages/device/lwm2m/security-config.models.ts +++ b/ui-ngx/src/app/shared/models/lwm2m-security-config.models.ts @@ -17,9 +17,6 @@ export const JSON_ALL_CONFIG = 'jsonAllConfig'; export const END_POINT = 'endPoint'; export const DEFAULT_END_POINT = 'default_client_lwm2m_end_point_no_sec'; -export const BOOTSTRAP_SERVERS = 'servers'; -export const BOOTSTRAP_SERVER = 'bootstrapServer'; -export const LWM2M_SERVER = 'lwm2mServer'; export const LEN_MAX_PSK = 64; export const LEN_MAX_PRIVATE_KEY = 134; export const LEN_MAX_PUBLIC_KEY_RPK = 182; @@ -28,28 +25,28 @@ export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; export interface DeviceCredentialsDialogLwm2mData { - jsonAllConfig?: SecurityConfigModels; + jsonAllConfig?: Lwm2mSecurityConfigModels; endPoint?: string; } -export enum SECURITY_CONFIG_MODE { +export enum Lwm2mSecurityType { PSK = 'PSK', RPK = 'RPK', X509 = 'X509', NO_SEC = 'NO_SEC' } -export const SECURITY_CONFIG_MODE_NAMES = new Map( +export const Lwm2mSecurityTypeTranslationMap = new Map( [ - [SECURITY_CONFIG_MODE.PSK, 'Pre-Shared Key'], - [SECURITY_CONFIG_MODE.RPK, 'Raw Public Key'], - [SECURITY_CONFIG_MODE.X509, 'X.509 Certificate'], - [SECURITY_CONFIG_MODE.NO_SEC, 'No Security'], + [Lwm2mSecurityType.PSK, 'Pre-Shared Key'], + [Lwm2mSecurityType.RPK, 'Raw Public Key'], + [Lwm2mSecurityType.X509, 'X.509 Certificate'], + [Lwm2mSecurityType.NO_SEC, 'No Security'], ] ); export interface ClientSecurityConfig { - securityConfigClientMode: string; + securityConfigClientMode: Lwm2mSecurityType; endpoint: string; identity: string; key: string; @@ -57,7 +54,7 @@ export interface ClientSecurityConfig { } export interface ServerSecurityConfig { - securityMode: string; + securityMode: Lwm2mSecurityType; clientPublicKeyOrId?: string; clientSecretKey?: string; } @@ -67,20 +64,19 @@ interface BootstrapSecurityConfig { lwm2mServer: ServerSecurityConfig; } -export interface SecurityConfigModels { +export interface Lwm2mSecurityConfigModels { client: ClientSecurityConfig; bootstrap: BootstrapSecurityConfig; } -export function getClientSecurityConfig(securityConfigMode: SECURITY_CONFIG_MODE, endPoint?: string): ClientSecurityConfig { - let security = getDefaultClientSecurityConfig(); - security.securityConfigClientMode = securityConfigMode.toString(); +export function getClientSecurityConfig(securityConfigMode: Lwm2mSecurityType, endPoint = ''): ClientSecurityConfig { + const security = getDefaultClientSecurityConfig(securityConfigMode); switch (securityConfigMode) { - case SECURITY_CONFIG_MODE.PSK: + case Lwm2mSecurityType.PSK: security.endpoint = endPoint; security.identity = endPoint; break; - case SECURITY_CONFIG_MODE.X509: + case Lwm2mSecurityType.X509: security.x509 = true; break; } @@ -88,9 +84,9 @@ export function getClientSecurityConfig(securityConfigMode: SECURITY_CONFIG_MODE return security; } -export function getDefaultClientSecurityConfig(): ClientSecurityConfig { +export function getDefaultClientSecurityConfig(securityConfigMode: Lwm2mSecurityType): ClientSecurityConfig { return { - securityConfigClientMode: SECURITY_CONFIG_MODE.NO_SEC.toString(), + securityConfigClientMode: securityConfigMode, endpoint: '', identity: '', key: '', @@ -100,7 +96,7 @@ export function getDefaultClientSecurityConfig(): ClientSecurityConfig { export function getDefaultServerSecurityConfig(): ServerSecurityConfig { return { - securityMode: SECURITY_CONFIG_MODE.NO_SEC.toString(), + securityMode: Lwm2mSecurityType.NO_SEC, clientPublicKeyOrId: '', clientSecretKey: '' }; @@ -113,46 +109,42 @@ function getDefaultBootstrapSecurityConfig(): BootstrapSecurityConfig { }; } -export function getDefaultSecurityConfig(): SecurityConfigModels { +export function getDefaultSecurityConfig(): Lwm2mSecurityConfigModels { const securityConfigModels = { - client: getClientSecurityConfig(SECURITY_CONFIG_MODE.NO_SEC), + client: getClientSecurityConfig(Lwm2mSecurityType.NO_SEC), bootstrap: getDefaultBootstrapSecurityConfig() }; return securityConfigModels; } -const isSecurityConfigModels = (p: any): p is SecurityConfigModels => +const isSecurityConfigModels = (p: any): boolean => p.hasOwnProperty('client') && - isClientSecurityConfigType(p['client']) && + isClientSecurityConfigType(p.client) && p.hasOwnProperty('bootstrap') && - isBootstrapSecurityConfig(p['bootstrap']); + isBootstrapSecurityConfig(p.bootstrap); -const isClientSecurityConfigType = (p: any): p is ClientSecurityConfig => +const isClientSecurityConfigType = (p: any): boolean => p.hasOwnProperty('securityConfigClientMode') && p.hasOwnProperty('endpoint') && p.hasOwnProperty('identity') && p.hasOwnProperty('key') && p.hasOwnProperty('x509'); -const isBootstrapSecurityConfig = (p: any): p is BootstrapSecurityConfig => +const isBootstrapSecurityConfig = (p: any): boolean => p.hasOwnProperty('bootstrapServer') && - isServerSecurityConfig(p['bootstrapServer']) && + isServerSecurityConfig(p.bootstrapServer) && p.hasOwnProperty('lwm2mServer') && - isServerSecurityConfig(p['lwm2mServer']); + isServerSecurityConfig(p.lwm2mServer); -const isServerSecurityConfig = (p: any): p is ServerSecurityConfig => +const isServerSecurityConfig = (p: any): boolean => p.hasOwnProperty('securityMode') && p.hasOwnProperty('clientPublicKeyOrId') && p.hasOwnProperty('clientSecretKey'); export function validateSecurityConfig(config: string): boolean { try { - const securityConfig= JSON.parse(config); - if (isSecurityConfigModels(securityConfig)) { - return true; - } else { - return false; - } + const securityConfig = JSON.parse(config); + return isSecurityConfigModels(securityConfig); } catch (e) { return false; } 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 08335a0175..bd7ec5e2a2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -928,9 +928,13 @@ "lwm2m-credentials-value-tip": "Edit security config json editor", "lwm2m-security-config": { "identity": "Client Identity", + "identity-required": "Client Identity is required.", "client-key": "Client Key", - "required": " value is required.", + "client-key-required": "Client Key is required.", + "client-key-pattern": "Client Key must be hexadecimal format.", + "client-key-length": "Client Key must be {{ count }} characters.", "endpoint": "Endpoint Client Name", + "endpoint-required": "Endpoint Client Name is required.", "mode": "Security config mode", "client-tab": "Client Security Config", "client-certificate": "Client certificate", @@ -938,9 +942,14 @@ "bootstrap-server": "Bootstrap Server", "lwm2m-server": "LwM2M Server", "client-publicKey-or-id": "Client Public Key or Id", + "client-publicKey-or-id-required": "Client Public Key or Id is required.", + "client-publicKey-or-id-pattern": "Client Public Key or Id must be hexadecimal format.", + "client-publicKey-or-id-length": "Client Public Key or Id must be {{ count }} characters.", "client-secret-key": "Client Secret Key", - "config-json-tab": "Json Client Security Config", - "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }" + "client-secret-key-required": "Client Secret Key is required.", + "client-secret-key-pattern": "Client Secret Key must be hexadecimal format.", + "client-secret-key-length": "Client Secret Key must be {{ count }} characters.", + "config-json-tab": "Json Client Security Config" }, "client-id": "Client ID", "client-id-pattern": "Contains invalid character.", From e33420d3a89264b3df099654eea8b3671ca0fbec Mon Sep 17 00:00:00 2001 From: nickAS21 <44275303+nickAS21@users.noreply.github.com> Date: Fri, 30 Apr 2021 08:39:06 +0300 Subject: [PATCH 108/127] Lwm2m: firmwareUpdate (#4516) * Lwm2m: firmwareUpdate * Lwm2m: firmwareUpdate with merge master * Lwm2m: firmwareUpdate cleaned * Lwm2m: delete Californium.properties * Lwm2m: merge with master --- .../resource/DefaultTbResourceService.java | 2 +- .../server/common/data/DataConstants.java | 4 +- ...TransportBootstrapServerConfiguration.java | 3 +- .../lwm2m/server/LwM2mNetworkConfig.java | 100 +++++++ .../lwm2m/server/LwM2mServerListener.java | 14 +- .../lwm2m/server/LwM2mTransportHandler.java | 33 +-- .../lwm2m/server/LwM2mTransportRequest.java | 123 +++++---- .../LwM2mTransportServerConfiguration.java | 8 +- .../lwm2m/server/LwM2mTransportService.java | 4 +- .../server/LwM2mTransportServiceImpl.java | 255 ++++++++++++------ .../server/adaptors/LwM2MJsonAdaptor.java | 21 +- .../adaptors/LwM2MTransportAdaptor.java | 4 +- .../lwm2m/server/client/LwM2mClient.java | 18 +- .../server/client/LwM2mClientContextImpl.java | 10 +- .../server/client/LwM2mFirmwareUpdate.java | 27 ++ .../lwm2m-observe-attr-telemetry.component.ts | 15 +- .../assets/locale/locale.constant-en_US.json | 2 +- 17 files changed, 435 insertions(+), 208 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java diff --git a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java index 81c59609ff..63c587405a 100644 --- a/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -185,7 +185,7 @@ public class DefaultTbResourceService implements TbResourceService { instance.setId(0); List resources = new ArrayList<>(); obj.resources.forEach((k, v) -> { - if (!v.operations.isExecutable()) { + if (v.operations.isReadable()) { LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); resources.add(lwM2MResourceObserve); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index d0fdf169fc..094069dafb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -95,8 +95,8 @@ public class DataConstants { //firmware //telemetry - public static final String CURRENT_FIRMWARE_TITLE = "cur_fw_title"; - public static final String CURRENT_FIRMWARE_VERSION = "cur_fw_version"; + public static final String CURRENT_FIRMWARE_TITLE = "current_fw_title"; + public static final String CURRENT_FIRMWARE_VERSION = "current_fw_version"; public static final String TARGET_FIRMWARE_TITLE = "target_fw_title"; public static final String TARGET_FIRMWARE_VERSION = "target_fw_version"; public static final String TARGET_FIRMWARE_TS = "target_fw_ts"; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java index e531de3628..9e40c736df 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java @@ -55,7 +55,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getCoapConfig; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; @Slf4j @Component @@ -93,7 +93,6 @@ public class LwM2MTransportBootstrapServerConfiguration { builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort)); /** Define model provider (Create Models )*/ -// builder.setModel(new StaticModel(contextS.getLwM2MTransportConfigServer().getModelsValueCommon())); /** Create credentials */ this.setServerWithCredentials(builder); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java new file mode 100644 index 0000000000..22f822bae6 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java @@ -0,0 +1,100 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server; + +import org.eclipse.californium.core.network.config.NetworkConfig; + +public class LwM2mNetworkConfig { + + public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort) { + NetworkConfig coapConfig = new NetworkConfig(); + coapConfig.setInt(NetworkConfig.Keys.COAP_PORT,serverPortNoSec); + coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT,serverSecurePort); + /** + * Example:Property for large packet: + * #NetworkConfig config = new NetworkConfig(); + * #config.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE,32); + * #config.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE,32); + * #config.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE,2048); + * #config.setInt(NetworkConfig.Keys.MAX_RETRANSMIT,3); + * #config.setInt(NetworkConfig.Keys.MAX_TRANSMIT_WAIT,120000); + */ + + /** + * Property to indicate if the response should always include the Block2 option \ + * when client request early blockwise negociation but the response can be sent on one packet. + * - value of false indicate that the server will respond without block2 option if no further blocks are required. + * - value of true indicate that the server will response with block2 option event if no further blocks are required. + * CoAP client will try to use block mode + * or adapt the block size when receiving a 4.13 Entity too large response code + */ + coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); + /*** + * Property to indicate if the response should always include the Block2 option \ + * when client request early blockwise negociation but the response can be sent on one packet. + * - value of false indicate that the server will respond without block2 option if no further blocks are required. + * - value of true indicate that the server will response with block2 option event if no further blocks are required. + */ + coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); + + coapConfig.setInt(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, 300000); + /** + * !!! REQUEST_ENTITY_TOO_LARGE CODE=4.13 + * The maximum size of a resource body (in bytes) that will be accepted + * as the payload of a POST/PUT or the response to a GET request in a + * transparent> blockwise transfer. + * This option serves as a safeguard against excessive memory + * consumption when many resources contain large bodies that cannot be + * transferred in a single CoAP message. This option has no impact on + * *manually* managed blockwise transfers in which the blocks are handled individually. + * Note that this option does not prevent local clients or resource + * implementations from sending large bodies as part of a request or response to a peer. + * The default value of this property is DEFAULT_MAX_RESOURCE_BODY_SIZE = 8192 + * A value of {@code 0} turns off transparent handling of blockwise transfers altogether. + */ +// coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 8192); + coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 16384); + /** + * The default DTLS response matcher. + * Supported values are STRICT, RELAXED, or PRINCIPAL. + * The default value is STRICT. + * Create new instance of udp endpoint context matcher. + * Params: + * checkAddress + * – true with address check, (STRICT, UDP) + * - false, without + */ + coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT"); + /** + * https://tools.ietf.org/html/rfc7959#section-2.9.3 + * The block size (number of bytes) to use when doing a blockwise transfer. \ + * This value serves as the upper limit for block size in blockwise transfers + */ + coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 512); + /** + * The maximum payload size (in bytes) that can be transferred in a + * single message, i.e. without requiring a blockwise transfer. + * NB: this value MUST be adapted to the maximum message size supported by the transport layer. + * In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol + * DEFAULT_VALUE = 1024 + */ + coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 512); + + coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); + + return coapConfig; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index c17e37fd4a..72626f1103 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -26,6 +26,7 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate; import java.util.Collection; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; @Slf4j @@ -85,17 +86,19 @@ public class LwM2mServerListener { @Override public void cancelled(Observation observation) { - log.info("Received notification cancelled from [{}] ", observation.getPath()); + String msg = String.format("%s: Cancel Observation %s.", LOG_LW2M_INFO, observation.getPath()); + service.sendLogsToThingsboard(msg, observation.getRegistrationId()); + log.trace(msg); } @Override public void onResponse(Observation observation, Registration registration, ObserveResponse response) { if (registration != null) { try { - service.onObservationResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), + service.onUpdateValueAfterReadResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), response, null); } catch (Exception e) { - log.error("[{}] onResponse", e.toString()); + log.error("Observation/Read onResponse", e); } } @@ -108,7 +111,10 @@ public class LwM2mServerListener { @Override public void newObservation(Observation observation, Registration registration) { - log.info("Received newObservation from [{}] endpoint [{}] ", observation.getPath(), registration.getEndpoint()); + String msg = String.format("%s: Successful start newObservation %s.", LOG_LW2M_INFO, + observation.getPath()); + service.sendLogsToThingsboard(msg, registration.getId()); + log.trace(msg); } }; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java index ac5adcb7a3..08e46eb288 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java @@ -25,7 +25,6 @@ import com.google.gson.JsonSyntaxException; import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.model.ObjectModel; @@ -40,7 +39,6 @@ import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.request.DownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.util.Hex; -import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.registration.Registration; import org.nustaq.serialization.FSTConfiguration; import org.thingsboard.server.common.data.DeviceProfile; @@ -50,7 +48,6 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; -import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -72,7 +69,6 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA @Slf4j public class LwM2mTransportHandler { - // public static final String BASE_DEVICE_API_TOPIC = "v1/devices/me"; public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0"; public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; public static final String BOOTSTRAP = "bootstrap"; @@ -85,19 +81,12 @@ public class LwM2mTransportHandler { public static final String KEY_NAME = "keyName"; public static final String OBSERVE_LWM2M = "observe"; public static final String ATTRIBUTE_LWM2M = "attributeLwm2m"; -// public static final String RESOURCE_VALUE = "resValue"; -// public static final String RESOURCE_TYPE = "resType"; private static final String REQUEST = "/request"; - // private static final String RESPONSE = "/response"; private static final String ATTRIBUTES = "/" + ATTRIBUTE; public static final String TELEMETRIES = "/" + TELEMETRY; - // public static final String ATTRIBUTES_RESPONSE = ATTRIBUTES + RESPONSE; public static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST; - // public static final String DEVICE_ATTRIBUTES_RESPONSE = ATTRIBUTES_RESPONSE + "/"; public static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/"; -// public static final String DEVICE_ATTRIBUTES_TOPIC = BASE_DEVICE_API_TOPIC + ATTRIBUTES; -// public static final String DEVICE_TELEMETRY_TOPIC = BASE_DEVICE_API_TOPIC + TELEMETRIES; public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms @@ -112,6 +101,11 @@ public class LwM2mTransportHandler { public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized"; + public static final Integer FR_OBJECT_ID = 5; + public static final Integer FR_RESOURCE_VER_ID = 7; + public static final String FR_PATH_RESOURCE_VER_ID = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_PATH + + "0" + LWM2M_SEPARATOR_PATH + FR_RESOURCE_VER_ID; + public enum LwM2mTypeServer { BOOTSTRAP(0, "bootstrap"), CLIENT(1, "client"); @@ -168,6 +162,8 @@ public class LwM2mTransportHandler { WRITE_ATTRIBUTES(8, "WriteAttributes"), DELETE(9, "Delete"); +// READ_INFO_FW(10, "ReadInfoFirmware"); + public int code; public String type; @@ -190,21 +186,6 @@ public class LwM2mTransportHandler { public static final String SERVICE_CHANNEL = "SERVICE"; public static final String RESPONSE_CHANNEL = "RESP"; - public static NetworkConfig getCoapConfig(Integer serverPortNoSec, Integer serverSecurePort) { - NetworkConfig coapConfig; - File configFile = new File(NetworkConfig.DEFAULT_FILE_NAME); - if (configFile.isFile()) { - coapConfig = new NetworkConfig(); - coapConfig.load(configFile); - } else { - coapConfig = LeshanServerBuilder.createDefaultNetworkConfig(); - coapConfig.store(configFile); - } - coapConfig.setString("COAP_PORT", Integer.toString(serverPortNoSec)); - coapConfig.setString("COAP_SECURE_PORT", Integer.toString(serverSecurePort)); - return coapConfig; - } - public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException { switch (type) { case BOOLEAN: diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index 9d7c22a112..370abaa9cd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -67,6 +67,7 @@ import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.FR_PATH_RESOURCE_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE; @@ -125,7 +126,6 @@ public class LwM2mTransportRequest { public void sendAllRequest(Registration registration, String targetIdVer, LwM2mTypeOper typeOper, String contentFormatName, Object params, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { try { - String target = convertPathFromIdVerToObjectId(targetIdVer); DownlinkRequest request = null; ContentFormat contentFormat = contentFormatName != null ? ContentFormat.fromName(contentFormatName.toUpperCase()) : ContentFormat.DEFAULT; @@ -145,11 +145,11 @@ public class LwM2mTransportRequest { break; case OBSERVE: if (resultIds.isResource()) { - request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); } else if (resultIds.isObjectInstance()) { - request = new ObserveRequest(resultIds.getObjectId(), resultIds.getObjectInstanceId()); + request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); } else if (resultIds.getObjectId() >= 0) { - request = new ObserveRequest(resultIds.getObjectId()); + request = new ObserveRequest(contentFormat, resultIds.getObjectId()); } break; case OBSERVE_CANCEL: @@ -171,8 +171,6 @@ public class LwM2mTransportRequest { break; case WRITE_REPLACE: // Request to write a String Single-Instance Resource using the TLV content format. -// resource = lwM2MClient.getResourceModel(targetIdVer); -// if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) { resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() .getModelProvider()); if (contentFormat.equals(ContentFormat.TLV)) { @@ -181,7 +179,6 @@ public class LwM2mTransportRequest { registration, rpcRequest); } // Mode.REPLACE && Request to write a String Single-Instance Resource using the given content format (TEXT, TLV, JSON) -// else if (!contentFormat.equals(ContentFormat.TLV) && !resource.multiple) { else if (!contentFormat.equals(ContentFormat.TLV)) { request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModel.type, @@ -215,13 +212,16 @@ public class LwM2mTransportRequest { long finalTimeoutInMs = timeoutInMs; lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, rpcRequest)); } catch (Exception e) { - log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper, e); + log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e); + } + } else if (OBSERVE_CANCEL == typeOper) { + log.trace("[{}], [{}] - [{}] SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer); + if (rpcRequest != null) { + rpcRequest.setInfoMsg(null); + serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), null, null); } - } else if (OBSERVE_CANCEL == typeOper && rpcRequest != null) { - rpcRequest.setInfoMsg(null); - serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), null, null); } else { - log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper, targetIdVer); + log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer); if (rpcRequest != null) { String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null"; serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); @@ -236,8 +236,8 @@ public class LwM2mTransportRequest { Set observationPaths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO, OBSERVE_READ_ALL.type, observationPaths); - serviceImpl.sendLogsToThingsboard(msg, registration); - log.info("[{}], [{}]", registration.getEndpoint(), msg); + serviceImpl.sendLogsToThingsboard(msg, registration.getId()); + log.trace("[{}] [{}], [{}]", typeOper.name(), registration.getEndpoint(), msg); if (rpcRequest != null) { String valueMsg = String.format("Observation paths - %s", observationPaths); serviceImpl.sentRpcRequest(rpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE); @@ -246,7 +246,7 @@ public class LwM2mTransportRequest { } catch (Exception e) { String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR, typeOper.name(), e.getMessage()); - serviceImpl.sendLogsToThingsboard(msg, registration); + serviceImpl.sendLogsToThingsboard(msg, registration.getId()); throw new Exception(e); } } @@ -261,45 +261,53 @@ public class LwM2mTransportRequest { private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { leshanServer.send(registration, request, timeoutInMs, (ResponseCallback) response -> { if (!lwM2MClient.isInit()) { - lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); + lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { this.handleResponse(registration, request.getPath().toString(), response, request, rpcRequest); - if (request instanceof WriteRequest && ((WriteRequest) request).isReplaceRequest()) { - LwM2mNode node = ((WriteRequest) request).getNode(); - Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(), - ((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath()); - String msg = String.format("%s: sendRequest Replace: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s SendRequest to Client", - LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), - response.getCode().getName(), request.getPath().toString(), value); - serviceImpl.sendLogsToThingsboard(msg, registration); - log.info("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(), - ((Response) response.getCoapResponse()).getCode(), response.getCode(), - request.getPath().toString(), value); - serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO); - } } else { - String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR, + String msg = String.format("%s: SendRequest %s: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s", LOG_LW2M_ERROR, request.getClass().getName().toString(), ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString()); - serviceImpl.sendLogsToThingsboard(msg, registration); - log.error("[{}], [{}] - [{}] [{}] error SendRequest", registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); + serviceImpl.sendLogsToThingsboard(msg, registration.getId()); + log.error("[{}] [{}], [{}] - [{}] [{}] error SendRequest", request.getClass().getName().toString(), registration.getEndpoint(), + ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); + if (!lwM2MClient.isInit()) { + lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); + } if (rpcRequest != null) { serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR); } + /** Not Found + * set setClient_fw_version = empty + **/ + if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) { + lwM2MClient.setUpdateFw(false); + lwM2MClient.getFrUpdate().setClientFwVersion(""); + log.warn("updateFirmwareClient1"); + serviceImpl.updateFirmwareClient(lwM2MClient); + } } }, e -> { + /** version == null + * set setClient_fw_version = empty + **/ + if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) { + lwM2MClient.setUpdateFw(false); + lwM2MClient.getFrUpdate().setClientFwVersion(""); + log.warn("updateFirmwareClient2"); + serviceImpl.updateFirmwareClient(lwM2MClient); + } if (!lwM2MClient.isInit()) { - lwM2MClient.initValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); + lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } - String msg = String.format("%s: sendRequest: Resource path - %s msg error - %s SendRequest to Client", - LOG_LW2M_ERROR, request.getPath().toString(), e.getMessage()); - serviceImpl.sendLogsToThingsboard(msg, registration); - log.error("[{}] - [{}] error SendRequest", request.getPath().toString(), e.toString()); + String msg = String.format("%s: SendRequest %s: Resource path - %s msg error - %s", + LOG_LW2M_ERROR, request.getClass().getName().toString(), request.getPath().toString(), e.getMessage()); + serviceImpl.sendLogsToThingsboard(msg, registration.getId()); + log.error("[{}] [{}] - [{}] error SendRequest", request.getClass().getName().toString(), request.getPath().toString(), e.toString()); if (rpcRequest != null) { serviceImpl.sentRpcRequest(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR); } }); - } private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, @@ -323,7 +331,9 @@ public class LwM2mTransportRequest { Date date = new Date(Long.decode(value.toString())); return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, date) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, date); case OPAQUE: // byte[] value, base64 - return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())) : new WriteRequest(contentFormat, objectId, instanceId, resourceId, Hex.decodeHex(value.toString().toCharArray())); + byte[] valueRequest = value instanceof byte[] ? (byte[]) value : Hex.decodeHex(value.toString().toCharArray()); + return (contentFormat == null) ? new WriteRequest(objectId, instanceId, resourceId, valueRequest) : + new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueRequest); default: } } @@ -337,7 +347,7 @@ public class LwM2mTransportRequest { String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; String msg = String.format(LOG_LW2M_ERROR + ": NumberFormatException: Resource path - %s type - %s value - %s msg error - %s SendRequest to Client", patn, type, value, e.toString()); - serviceImpl.sendLogsToThingsboard(msg, registration); + serviceImpl.sendLogsToThingsboard(msg, registration.getId()); log.error("Path: [{}] type: [{}] value: [{}] errorMsg: [{}]]", patn, type, value, e.toString()); if (rpcRequest != null) { String errorMsg = String.format("NumberFormatException: Resource path - %s type - %s value - %s", patn, type, value); @@ -369,7 +379,7 @@ public class LwM2mTransportRequest { DownlinkRequest request, Lwm2mClientRpcRequest rpcRequest) { String pathIdVer = convertPathFromObjectIdToIdVer(path, registration); if (response instanceof ReadResponse) { - serviceImpl.onObservationResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest); + serviceImpl.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest); } else if (response instanceof CancelObservationResponse) { log.info("[{}] Path [{}] CancelObservationResponse 3_Send", pathIdVer, response); @@ -389,14 +399,33 @@ public class LwM2mTransportRequest { } else if (response instanceof WriteAttributesResponse) { log.info("[{}] Path [{}] WriteAttributesResponse 8_Send", pathIdVer, response); } else if (response instanceof WriteResponse) { - log.info("[{}] Path [{}] WriteAttributesResponse 9_Send", pathIdVer, response); + log.info("[{}] Path [{}] WriteResponse 9_Send", pathIdVer, response); + this.infoWriteResponse(registration, response, request); serviceImpl.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request); } - if (rpcRequest != null && (response instanceof ExecuteResponse - || response instanceof WriteAttributesResponse - || response instanceof DeleteResponse)) { - rpcRequest.setInfoMsg(null); - serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null); + if (rpcRequest != null) { + if (response instanceof ExecuteResponse + || response instanceof WriteAttributesResponse + || response instanceof DeleteResponse) { + rpcRequest.setInfoMsg(null); + serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, null); + } else if (response instanceof WriteResponse) { + serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), null, LOG_LW2M_INFO); + } } } + + private void infoWriteResponse(Registration registration, LwM2mResponse response, + DownlinkRequest request) { + LwM2mNode node = ((WriteRequest) request).getNode(); + Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(), + ((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath()); + String msg = String.format("%s: Update finished successfully: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s", + LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), + response.getCode().getName(), request.getPath().toString(), value); + serviceImpl.sendLogsToThingsboard(msg, registration.getId()); + log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName().toString(), registration.getEndpoint(), + ((Response) response.getCoapResponse()).getCode(), response.getCode(), + request.getPath().toString(), value); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java index 0af20b3e51..86bec24984 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java @@ -16,6 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.core.network.stack.BlockwiseLayer; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; @@ -57,7 +59,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CBC_SHA256; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_PSK_WITH_AES_128_CCM_8; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getCoapConfig; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.getCoapConfig; @Slf4j @Component @@ -92,7 +94,10 @@ public class LwM2mTransportServerConfiguration { /** Use a magic converter to support bad type send by the UI. */ builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); + /** Create CoAP Config */ + NetworkConfig networkConfig = getCoapConfig(serverPortNoSec, serverSecurePort); + BlockwiseLayer blockwiseLayer = new BlockwiseLayer(networkConfig); builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort)); /** Define model provider (Create Models )*/ @@ -110,6 +115,7 @@ public class LwM2mTransportServerConfiguration { /** Create DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); + dtlsConfig.setServerOnly(true); dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getLwM2MTransportConfigServer().isRecommendedSupportedGroups()); dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getLwM2MTransportConfigServer().isRecommendedCiphers()); if (this.pskMode) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java index 91588c0490..dec1e7889d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java @@ -39,7 +39,7 @@ public interface LwM2mTransportService extends TbTransportService { void setCancelObservations(Registration registration); - void onObservationResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest); + void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest); void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo); @@ -60,6 +60,4 @@ public interface LwM2mTransportService extends TbTransportService { void doTrigger(Registration registration, String path); void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); - - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java index 31ae4bdf83..f52919ea3c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java @@ -38,10 +38,12 @@ import org.eclipse.leshan.server.registration.Registration; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cache.firmware.FirmwareDataCache; 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.id.FirmwareId; import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.gen.transport.TransportProtos; @@ -77,9 +79,12 @@ import java.util.stream.Collectors; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; +import static org.thingsboard.server.common.data.DataConstants.FIRMWARE_VERSION; +import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.FR_PATH_RESOURCE_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE; @@ -110,6 +115,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { private ExecutorService executorUpdateRegistered; private ExecutorService executorUnRegistered; private LwM2mValueConverterImpl converter; + private FirmwareDataCache firmwareDataCache; + private final TransportService transportService; @@ -121,12 +128,15 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { private final LwM2mTransportRequest lwM2mTransportRequest; - public LwM2mTransportServiceImpl(TransportService transportService, LwM2mTransportContextServer lwM2mTransportContextServer, LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, @Lazy LwM2mTransportRequest lwM2mTransportRequest) { + public LwM2mTransportServiceImpl(TransportService transportService, LwM2mTransportContextServer lwM2mTransportContextServer, + LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, + @Lazy LwM2mTransportRequest lwM2mTransportRequest, FirmwareDataCache firmwareDataCache) { this.transportService = transportService; this.lwM2mTransportContextServer = lwM2mTransportContextServer; this.lwM2mClientContext = lwM2mClientContext; this.leshanServer = leshanServer; this.lwM2mTransportRequest = lwM2mTransportRequest; + this.firmwareDataCache = firmwareDataCache; } @PostConstruct @@ -168,8 +178,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), null); + this.getInfoFirmwareUpdate(lwM2MClient); this.initLwM2mFromClientValue(registration, lwM2MClient); - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration); + this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client create after Registration", registration.getId()); } else { log.error("Client: [{}] onRegistered [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); } @@ -224,7 +235,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { executorUnRegistered.submit(() -> { try { this.setCancelObservations(registration); - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration); + this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId()); this.closeClientSession(registration); } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); @@ -257,7 +268,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void onSleepingDev(Registration registration) { log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is sleeping!", registration); + this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is sleeping!", registration.getId()); //TODO: associate endpointId with device information. } @@ -280,7 +291,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param response - observe */ @Override - public void onObservationResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) { + public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) { if (response.getContent() != null) { if (response.getContent() instanceof LwM2mObject) { LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); @@ -304,20 +315,26 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { /** * Update - send request in change value resources in Client - * Path to resources from profile equal keyName or from ModelObject equal name - * Only for resources: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase) - * Delete - nothing * + * 1. FirmwareUpdate: + * - If msg.getSharedUpdatedList().forEach(tsKvProto -> {tsKvProto.getKv().getKey().indexOf(FIRMWARE_UPDATE_PREFIX, 0) == 0 + * 2. Shared Other AttributeUpdate + * -- Path to resources from profile equal keyName or from ModelObject equal name + * -- Only for resources: isWritable && isPresent as attribute in profile -> LwM2MClientProfile (format: CamelCase) + * 3. Delete - nothing * * @param msg - */ @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { + LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); if (msg.getSharedUpdatedCount() > 0) { msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); + if (FIRMWARE_VERSION.equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { + this.getInfoFirmwareUpdate(lwM2MClient); + } if (pathIdVer != null) { ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() .getModelProvider()); @@ -327,19 +344,26 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { log.error("Resource path - [{}] value - [{}] is not Writable and cannot be updated", pathIdVer, valueNew); String logMsg = String.format("%s: attributeUpdate: Resource path - %s value - %s is not Writable and cannot be updated", LOG_LW2M_ERROR, pathIdVer, valueNew); - this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId()); } } else { log.error("Resource name name - [{}] value - [{}] is not present as attribute/telemetry in profile and cannot be updated", pathName, valueNew); String logMsg = String.format("%s: attributeUpdate: attribute name - %s value - %s is not present as attribute in profile and cannot be updated", LOG_LW2M_ERROR, pathName, valueNew); - this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId()); } + }); } else if (msg.getSharedDeletedCount() > 0) { + msg.getSharedUpdatedList().forEach(tsKvProto -> { + String pathName = tsKvProto.getKv().getKey(); + Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); + if (FIRMWARE_VERSION.equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { + lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew); + } + }); log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); } - } /** @@ -455,23 +479,21 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonObject()) { lwm2mClientRpcRequest.setParams(new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey) - .getAsJsonObject().toString(), new TypeToken>() { - }.getType())); + .getAsJsonObject().toString(), new TypeToken>() { + }.getType())); } lwm2mClientRpcRequest.setSessionInfo(sessionInfo); if (OBSERVE_READ_ALL != lwm2mClientRpcRequest.getTypeOper() && lwm2mClientRpcRequest.getTargetIdVer() == null) { lwm2mClientRpcRequest.setErrorMsg(lwm2mClientRpcRequest.targetIdVerKey + " and " + lwm2mClientRpcRequest.keyNameKey + " is null or bad format"); - } - else if ((EXECUTE == lwm2mClientRpcRequest.getTypeOper() + } else if ((EXECUTE == lwm2mClientRpcRequest.getTypeOper() || WRITE_REPLACE == lwm2mClientRpcRequest.getTypeOper()) - && lwm2mClientRpcRequest.getTargetIdVer() !=null + && lwm2mClientRpcRequest.getTargetIdVer() != null && !(new LwM2mPath(convertPathFromIdVerToObjectId(lwm2mClientRpcRequest.getTargetIdVer())).isResource() || new LwM2mPath(convertPathFromIdVerToObjectId(lwm2mClientRpcRequest.getTargetIdVer())).isResourceInstance())) { - lwm2mClientRpcRequest.setErrorMsg("Invalid parameter " + lwm2mClientRpcRequest.targetIdVerKey + lwm2mClientRpcRequest.setErrorMsg("Invalid parameter " + lwm2mClientRpcRequest.targetIdVerKey + ". Only Resource or ResourceInstance can be this operation"); - } - else if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()){ + } else if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()) { lwm2mClientRpcRequest.setErrorMsg("Procedures In Development..."); } } else { @@ -483,23 +505,23 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { return lwm2mClientRpcRequest; } - public void sentRpcRequest (Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { + public void sentRpcRequest(Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { rpcRequest.setResponseCode(requestCode); - if (LOG_LW2M_ERROR.equals(typeMsg)) { - rpcRequest.setInfoMsg(null); - rpcRequest.setValueMsg(null); - if (rpcRequest.getErrorMsg() == null) { - msg = msg.isEmpty() ? null : msg; - rpcRequest.setErrorMsg(msg); - } - } else if (LOG_LW2M_INFO.equals(typeMsg)) { - if (rpcRequest.getInfoMsg() == null) { - rpcRequest.setInfoMsg(msg); - } - } else if (LOG_LW2M_VALUE.equals(typeMsg)) { - if (rpcRequest.getValueMsg() == null) { - rpcRequest.setValueMsg(msg); - } + if (LOG_LW2M_ERROR.equals(typeMsg)) { + rpcRequest.setInfoMsg(null); + rpcRequest.setValueMsg(null); + if (rpcRequest.getErrorMsg() == null) { + msg = msg.isEmpty() ? null : msg; + rpcRequest.setErrorMsg(msg); + } + } else if (LOG_LW2M_INFO.equals(typeMsg)) { + if (rpcRequest.getInfoMsg() == null) { + rpcRequest.setInfoMsg(msg); + } + } else if (LOG_LW2M_VALUE.equals(typeMsg)) { + if (rpcRequest.getValueMsg() == null) { + rpcRequest.setValueMsg(msg); + } } this.onToDeviceRpcResponse(rpcRequest.getDeviceRpcResponseResultMsg(), rpcRequest.getSessionInfo()); } @@ -556,7 +578,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { */ protected void onAwakeDev(Registration registration) { log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); - this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration); + this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId()); //TODO: associate endpointId with device information. } @@ -583,10 +605,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { /** * @param logMsg - text msg - * @param registration - Id of Registration LwM2M Client + * @param registrationId - Id of Registration LwM2M Client */ - public void sendLogsToThingsboard(String logMsg, Registration registration) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + public void sendLogsToThingsboard(String logMsg, String registrationId) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId); if (logMsg != null && sessionInfo != null) { this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(this.lwM2mTransportContextServer.getKvLogyToThingsboard(logMsg), sessionInfo); } @@ -610,7 +632,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (clientObjects != null && clientObjects.size() > 0) { if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { // #2 - lwM2MClient.getPendingRequests().addAll(clientObjects); + lwM2MClient.getPendingReadRequests().addAll(clientObjects); clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); } @@ -652,6 +674,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * Sending observe value of resources to thingsboard * #1 Return old Value Resource from LwM2MClient * #2 Update new Resources (replace old Resource Value on new Resource Value) + * #3 If fr_update -> UpdateFirmware + * #4 updateAttrTelemetry * * @param registration - Registration LwM2M Client * @param lwM2mResource - LwM2mSingleResource response.getContent() @@ -661,6 +685,19 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() .getModelProvider())) { + if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && + lwM2MClient.getFrUpdate().getCurrentFwVersion() != null + && !lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion()) + && lwM2MClient.isUpdateFw()) { + + /** version != null + * set setClient_fw_version = value + **/ + lwM2MClient.setUpdateFw(false); + lwM2MClient.getFrUpdate().setClientFwVersion(lwM2mResource.getValue().toString()); + log.warn("updateFirmwareClient3"); + this.updateFirmwareClient(lwM2MClient); + } Set paths = new HashSet<>(); paths.add(path); this.updateAttrTelemetry(registration, paths); @@ -669,6 +706,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } } + /** * send Attribute and Telemetry to Thingsboard * #1 - get AttrName/TelemetryName with value from LwM2MClient: @@ -723,22 +761,23 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()); result = params.keySet(); } - if (!result.isEmpty()) { + if (result != null && !result.isEmpty()) { // #1 Set pathSend = result.stream().filter(target -> { return target.split(LWM2M_SEPARATOR_PATH).length < 3 ? clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1]) : clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1] + "/" + target.split(LWM2M_SEPARATOR_PATH)[2]); } - ) - .collect(Collectors.toUnmodifiableSet()); + ).collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { - lwM2MClient.getPendingRequests().addAll(pathSend); + lwM2MClient.getPendingReadRequests().addAll(pathSend); ConcurrentHashMap finalParams = params; - pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), - finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); + pathSend.forEach(target -> { + lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), + finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + }); if (OBSERVE.equals(typeOper)) { - lwM2MClient.initValue(this, null); + lwM2MClient.initReadValue(this, null); } } } @@ -969,7 +1008,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { // update value in Resources registrationIds.forEach(registrationId -> { Registration registration = lwM2mClientContext.getRegistration(registrationId); - this.readResourceValueObserve(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ); + this.readObserveFromProfile(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ); // send attr/telemetry to tingsboard for new path this.updateAttrTelemetry(registration, sendAttrToThingsboard.getPathPostParametersAdd()); }); @@ -998,12 +1037,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { registrationIds.forEach(registrationId -> { Registration registration = lwM2mClientContext.getRegistration(registrationId); if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) { - this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE); + this.readObserveFromProfile(registration, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE); } // 5.3 del // send Request cancel observe to Client if (postObserveAnalyzer.getPathPostParametersDel().size() > 0) { - this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel()); + this.cancelObserveFromProfile(registration, postObserveAnalyzer.getPathPostParametersDel()); } }); } @@ -1043,7 +1082,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param registration - Registration LwM2M Client * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] */ - private void readResourceValueObserve(Registration registration, Set targets, LwM2mTypeOper typeOper) { + private void readObserveFromProfile(Registration registration, Set targets, LwM2mTypeOper typeOper) { targets.forEach(target -> { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(target)); if (pathIds.isResource()) { @@ -1133,7 +1172,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } - private void cancelObserveIsValue(Registration registration, Set paramAnallyzer) { + private void cancelObserveFromProfile(Registration registration, Set paramAnallyzer) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); paramAnallyzer.forEach(pathIdVer -> { if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) { @@ -1153,7 +1192,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { log.error("Failed update resource [{}] [{}]", path, valueNew); String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", LOG_LW2M_ERROR, path, valueNew); - this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration()); + this.sendLogsToThingsboard(logMsg, lwM2MClient.getRegistration().getId()); log.info("Failed update resource [{}] [{}]", path, valueNew); } } @@ -1182,8 +1221,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } /** - * Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values - * #1 Get path resource by result attributesResponse + * 1. FirmwareUpdate: + * - msg.getSharedUpdatedList().forEach(tsKvProto -> {tsKvProto.getKv().getKey().indexOf(FIRMWARE_UPDATE_PREFIX, 0) == 0 + * 2. Update resource value on client: if there is a difference in values between the current resource values and the shared attribute values + * - Get path resource by result attributesResponse * * @param attributesResponse - * @param sessionInfo - @@ -1191,6 +1232,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { try { List tsKvProtos = attributesResponse.getSharedAttributeListList(); + this.updateAttriuteFromThingsboard(tsKvProtos, sessionInfo); } catch (Exception e) { log.error(String.valueOf(e)); @@ -1274,7 +1316,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { */ private SessionInfoProto getValidateSessionInfo(String registrationId) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(null, registrationId); - return getNewSessionInfoProto(lwM2MClient); + return lwM2MClient != null ? this.getNewSessionInfoProto(lwM2MClient) : null; } /** @@ -1293,28 +1335,88 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } /** - * !!! sharedAttr === profileAttr !!! - * If there is a difference in values between the current resource values and the shared attribute values - * when the client connects to the server - * #1 get attributes name from profile include name resources in ModelObject if resource isWritable - * #2.1 #1 size > 0 => send Request getAttributes to thingsboard + * #1. !!! sharedAttr === profileAttr !!! + * - If there is a difference in values between the current resource values and the shared attribute values + * - when the client connects to the server + * #1.1 get attributes name from profile include name resources in ModelObject if resource isWritable + * #1.2 #1 size > 0 => send Request getAttributes to thingsboard + * #2. FirmwareAttribute subscribe: * * @param lwM2MClient - LwM2M Client */ public void putDelayedUpdateResourcesThingsboard(LwM2mClient lwM2MClient) { SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); if (sessionInfo != null) { - //#1.1 + #1.2 - List attrSharedNames = this.getNamesAttrFromProfileIsWritable(lwM2MClient); - if (attrSharedNames.size() > 0) { - //#2.1 + //#1.1 + ConcurrentMap keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); + if (keyNamesMap.values().size() > 0) { try { - TransportProtos.GetAttributeRequestMsg getAttributeMsg = lwM2mTransportContextServer.getAdaptor().convertToGetAttributes(null, attrSharedNames); + //#1.2 + TransportProtos.GetAttributeRequestMsg getAttributeMsg = lwM2mTransportContextServer.getAdaptor().convertToGetAttributes(null, keyNamesMap.values()); transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); } catch (AdaptorException e) { log.warn("Failed to decode get attributes request", e); } } + + } + } + + public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) { + SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); + if (sessionInfo != null) { + TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .build(); + transportService.process(sessionInfo, getFirmwareRequestMsg, + new TransportServiceCallback<>() { + @Override + public void onSuccess(TransportProtos.GetFirmwareResponseMsg response) { + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { + lwM2MClient.getFrUpdate().setCurrentFwVersion(response.getVersion()); + lwM2MClient.getFrUpdate().setCurrentFwId(new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())).getId()); + lwM2MClient.setUpdateFw(true); + readRequestToClientFirmwareVer(lwM2MClient.getRegistration()); + } else { + log.trace("Firmware [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); + } + } + + @Override + public void onError(Throwable e) { + log.trace("Failed to process credentials ", e); + } + }); + } + } + + /** + * @param registration + */ + public void readRequestToClientFirmwareVer(Registration registration) { + String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration); + lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, READ, ContentFormat.TLV.getName(), + null, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + } + + /** + * + * @param lwM2MClient - + */ + public void updateFirmwareClient(LwM2mClient lwM2MClient) { + if (!lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion())) { + int chunkSize = 0; + int chunk = 0; + byte[] firmwareChunk = firmwareDataCache.get(lwM2MClient.getFrUpdate().getCurrentFwId().toString(), chunkSize, chunk); + Integer objectId = 5; + String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(objectId); + String targetIdVer = LWM2M_SEPARATOR_PATH + objectId + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0; + lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.TLV.getName(), + firmwareChunk, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion()); } } @@ -1326,23 +1428,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param lwM2MClient - * @return ArrayList keyNames from profile profileAttr && IsWritable */ - private List getNamesAttrFromProfileIsWritable(LwM2mClient lwM2MClient) { + private ConcurrentMap getNamesFromProfileForSharedAttributes(LwM2mClient lwM2MClient) { + LwM2mClientProfile profile = lwM2mClientContext.getProfile(lwM2MClient.getProfileId()); - Set attrSet = new Gson().fromJson(profile.getPostAttributeProfile(), - new TypeToken>() { - }.getType()); - ConcurrentMap keyNamesMap = new Gson().fromJson(profile.getPostKeyNameProfile().toString(), + return new Gson().fromJson(profile.getPostKeyNameProfile().toString(), new TypeToken>() { }.getType()); - - ConcurrentMap keyNamesIsWritable = keyNamesMap.entrySet() - .stream() - .filter(e -> (attrSet.contains(e.getKey()) && validateResourceInModel(lwM2MClient, e.getKey(), true))) - .collect(Collectors.toConcurrentMap(Map.Entry::getKey, Map.Entry::getValue)); - - Set namesIsWritable = ConcurrentHashMap.newKeySet(); - namesIsWritable.addAll(new HashSet<>(keyNamesIsWritable.values())); - return new ArrayList<>(namesIsWritable); } private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java index b228a52b94..65a1e14a4c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MJsonAdaptor.java @@ -24,11 +24,8 @@ import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; +import java.util.Collection; import java.util.Random; -import java.util.Set; @Slf4j @Component("LwM2MJsonAdaptor") @@ -54,11 +51,7 @@ public class LwM2MJsonAdaptor implements LwM2MTransportAdaptor { } @Override - public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(List clientKeys, List sharedKeys) throws AdaptorException { - return processGetAttributeRequestMsg(clientKeys, sharedKeys); - } - - protected TransportProtos.GetAttributeRequestMsg processGetAttributeRequestMsg(List clientKeys, List sharedKeys) throws AdaptorException { + public TransportProtos.GetAttributeRequestMsg convertToGetAttributes(Collection clientKeys, Collection sharedKeys) throws AdaptorException { try { TransportProtos.GetAttributeRequestMsg.Builder result = TransportProtos.GetAttributeRequestMsg.newBuilder(); Random random = new Random(); @@ -75,14 +68,4 @@ public class LwM2MJsonAdaptor implements LwM2MTransportAdaptor { throw new AdaptorException(e); } } - - private Set toStringSet(JsonElement requestBody, String name) { - JsonElement element = requestBody.getAsJsonObject().get(name); - if (element != null) { - return new HashSet<>(Arrays.asList(element.getAsString().split(","))); - } else { - return null; - } - } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java index 4b6d54c874..d654475f03 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/adaptors/LwM2MTransportAdaptor.java @@ -19,7 +19,7 @@ import com.google.gson.JsonElement; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos; -import java.util.List; +import java.util.Collection; public interface LwM2MTransportAdaptor { @@ -27,5 +27,5 @@ public interface LwM2MTransportAdaptor { TransportProtos.PostAttributeMsg convertToPostAttributes(JsonElement jsonElement) throws AdaptorException; - TransportProtos.GetAttributeRequestMsg convertToGetAttributes(List clientKeys, List sharedKeys) throws AdaptorException; + TransportProtos.GetAttributeRequestMsg convertToGetAttributes(Collection clientKeys, Collection sharedKeys) throws AdaptorException; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index ac1738e101..0617847246 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -57,13 +57,15 @@ public class LwM2mClient implements Cloneable { private UUID deviceId; private UUID sessionId; private UUID profileId; + private volatile LwM2mFirmwareUpdate frUpdate; private Registration registration; private ValidateDeviceCredentialsResponseMsg credentialsResponse; private final Map resources; private final Map delayedRequests; - private final List pendingRequests; + private final List pendingReadRequests; private final Queue queuedRequests; private boolean init; + private volatile boolean updateFw; public Object clone() throws CloneNotSupportedException { return super.clone(); @@ -75,12 +77,14 @@ public class LwM2mClient implements Cloneable { this.securityInfo = securityInfo; this.credentialsResponse = credentialsResponse; this.delayedRequests = new ConcurrentHashMap<>(); - this.pendingRequests = new CopyOnWriteArrayList<>(); + this.pendingReadRequests = new CopyOnWriteArrayList<>(); this.resources = new ConcurrentHashMap<>(); this.profileId = profileId; this.sessionId = sessionId; this.init = false; + this.updateFw = false; this.queuedRequests = new ConcurrentLinkedQueue<>(); + this.frUpdate = new LwM2mFirmwareUpdate(); } public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) { @@ -103,15 +107,13 @@ public class LwM2mClient implements Cloneable { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verRez = getVerFromPathIdVerOrId(pathRez); - return (verRez == null || verSupportedObject.equals(verRez)) ? modelProvider.getObjectModel(registration) + return verRez == null || verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) .getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null; } public Collection getNewResourcesForInstance(String pathRezIdVer, LwM2mModelProvider modelProvider, LwM2mValueConverterImpl converter) { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); - String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); - String verRez = getVerFromPathIdVerOrId(pathRezIdVer); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources; @@ -170,11 +172,11 @@ public class LwM2mClient implements Cloneable { .collect(Collectors.toSet()); } - public void initValue(LwM2mTransportServiceImpl serviceImpl, String path) { + public void initReadValue(LwM2mTransportServiceImpl serviceImpl, String path) { if (path != null) { - this.pendingRequests.remove(path); + this.pendingReadRequests.remove(path); } - if (this.pendingRequests.size() == 0) { + if (this.pendingReadRequests.size() == 0) { this.init = true; serviceImpl.putDelayedUpdateResourcesThingsboard(this); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index f496ed01b4..fe17ac3819 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -82,12 +82,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public LwM2mClient getLwM2mClientWithReg(Registration registration, String registrationId) { - LwM2mClient client = registrationId != null ? + LwM2mClient client = registrationId != null && this.lwM2mClients.containsKey(registrationId) ? this.lwM2mClients.get(registrationId) : - this.lwM2mClients.containsKey(registration.getId()) ? - this.lwM2mClients.get(registration.getId()) : - this.lwM2mClients.get(registration.getEndpoint()); - return client != null ? client : updateInSessionsLwM2MClient(registration); + registration !=null && this.lwM2mClients.containsKey(registration.getId()) ? + this.lwM2mClients.get(registration.getId()) : registration !=null && this.lwM2mClients.containsKey(registration) ? + this.lwM2mClients.get(registration.getEndpoint()) : null; + return client != null ? client : registration!= null ? updateInSessionsLwM2MClient(registration) : null; } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java new file mode 100644 index 0000000000..9eb6321445 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFirmwareUpdate.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server.client; + +import lombok.Data; + +import java.util.UUID; + +@Data +public class LwM2mFirmwareUpdate { + private volatile String clientFwVersion; + private volatile String currentFwVersion; + private volatile UUID currentFwId; +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts index 77a5956855..3d6b93b84c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.ts @@ -24,6 +24,8 @@ import { NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { ATTRIBUTE, @@ -38,6 +40,7 @@ import { } from './lwm2m-profile-config.models'; import { deepClone, isDefinedAndNotNull, isEqual, isUndefined } from '@core/utils'; import { MatDialog } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; import { Lwm2mObjectAddInstancesData, Lwm2mObjectAddInstancesDialogComponent @@ -80,8 +83,10 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor @Input() disabled: boolean; - constructor(private fb: FormBuilder, - private dialog: MatDialog) { + constructor(private store: Store, + private fb: FormBuilder, + private dialog: MatDialog, + public translate: TranslateService) { this.observeAttrTelemetryFormGroup = this.fb.group({ [CLIENT_LWM2M]: this.fb.array([]) }); @@ -93,7 +98,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor } private propagateChange = (v: any) => { - } + }; registerOnChange(fn: any): void { this.propagateChange = fn; @@ -184,7 +189,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M).updateValueAndValidity(); } - trackByParams = (index: number): number => { + trackByParams = (index: number, element: any): number => { return index; } @@ -312,7 +317,7 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor return objectName + ' <' + idVerObj + '>'; } getNameInstanceLwm2m = (instance: Instance, idVerObj: string): string => { - return ` instance <${idVerObj}/${instance.id}>`; + return ' instance <' + idVerObj + '/' + instance.id +'>'; } updateAttributeLwm2mObject = (event: Event, objectKeyId: number): void => { 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 bd7ec5e2a2..9c2253d0ab 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1205,7 +1205,7 @@ "telemetry-label": "Telemetry", "key-name-label": "Key Name", "attribute-lwm2m-label": "AttrLwm2m", - "resource-tip": "ID & Original Name of the Resource", + "resource-tip": "ID & Original Name of the Resource (only Operations isReadable)", "is-observe-tip": "Is Observe", "not-observe-tip": "To observe select telemetry or attributes first", "is-attr-tip": "Is Attribute", From e6d3b1d3f69f9715bb26c04616a5142c3ee65192 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 29 Apr 2021 14:22:43 +0300 Subject: [PATCH 109/127] Fix for getting user method when additionalInfo is null and refactoring --- .../server/controller/UserController.java | 24 +++++++------------ 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index f254414902..9daacc3027 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -32,6 +32,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; @@ -89,13 +90,14 @@ public class UserController extends BaseController { try { UserId userId = new UserId(toUUID(strUserId)); User user = checkUserId(userId, Operation.READ); - if(!user.getAdditionalInfo().isNull()) { - processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), DEFAULT_DASHBOARD); - processDashboardIdFromAdditionalInfo((ObjectNode) user.getAdditionalInfo(), HOME_DASHBOARD); - } - UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); - if(userCredentials.isEnabled()) { - addUserCredentialsEnabled((ObjectNode) user.getAdditionalInfo()); + if(user.getAdditionalInfo().isObject()) { + ObjectNode additionalInfo = (ObjectNode) user.getAdditionalInfo(); + processDashboardIdFromAdditionalInfo(additionalInfo, DEFAULT_DASHBOARD); + processDashboardIdFromAdditionalInfo(additionalInfo, HOME_DASHBOARD); + UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); + if(userCredentials.isEnabled() && !additionalInfo.has("userCredentialsEnabled")) { + additionalInfo.put("userCredentialsEnabled", true); + } } return user; } catch (Exception e) { @@ -103,14 +105,6 @@ public class UserController extends BaseController { } } - private void addUserCredentialsEnabled(ObjectNode additionalInfo) { - if(!additionalInfo.isNull()) { - if(!additionalInfo.has("userCredentialsEnabled")) { - additionalInfo.put("userCredentialsEnabled", true); - } - } - } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET) @ResponseBody From f264aa43302dd70f9dfd39cd8475939889a117de Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 29 Apr 2021 10:02:51 +0300 Subject: [PATCH 110/127] fixed firmware tests and validation --- .../server/controller/FirmwareController.java | 15 +- .../firmware/DefaultFirmwareStateService.java | 16 +- .../transport/DefaultTransportApiService.java | 1 + .../BaseFirmwareControllerTest.java | 30 ++- .../controller/ControllerSqlTestSuite.java | 2 +- .../server/dao/firmware/FirmwareService.java | 4 +- .../server/common/data/Device.java | 12 ++ .../server/common/data/DeviceProfile.java | 4 + .../server/common/data/FirmwareInfo.java | 3 + .../dao/device/DeviceProfileServiceImpl.java | 10 + .../server/dao/device/DeviceServiceImpl.java | 16 ++ .../dao/firmware/BaseFirmwareService.java | 92 +++++---- .../server/dao/firmware/FirmwareInfoDao.java | 4 +- .../server/dao/model/ModelConstants.java | 3 + .../dao/model/sql/AbstractDeviceEntity.java | 16 +- .../dao/model/sql/DeviceProfileEntity.java | 18 +- .../server/dao/model/sql/FirmwareEntity.java | 7 + .../dao/model/sql/FirmwareInfoEntity.java | 13 +- .../sql/firmware/FirmwareInfoRepository.java | 19 +- .../dao/sql/firmware/JpaFirmwareInfoDao.java | 8 +- .../resources/sql/schema-entities-hsql.sql | 9 +- .../main/resources/sql/schema-entities.sql | 10 +- .../service/BaseDeviceProfileServiceTest.java | 5 + .../dao/service/BaseDeviceServiceTest.java | 43 ++++ .../dao/service/BaseFirmwareServiceTest.java | 185 ++++++++++++++++-- 25 files changed, 456 insertions(+), 89 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java index 9c08967c8e..9b3caf7a79 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -35,6 +35,8 @@ import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -133,6 +135,8 @@ public class FirmwareController extends BaseController { Firmware firmware = new Firmware(firmwareId); firmware.setCreatedTime(info.getCreatedTime()); firmware.setTenantId(getTenantId()); + firmware.setDeviceProfileId(info.getDeviceProfileId()); + firmware.setType(info.getType()); firmware.setTitle(info.getTitle()); firmware.setVersion(info.getVersion()); firmware.setAdditionalInfo(info.getAdditionalInfo()); @@ -175,17 +179,22 @@ public class FirmwareController extends BaseController { } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/firmwares/{hasData}", method = RequestMethod.GET) + @RequestMapping(value = "/firmwares/{deviceProfileId}/{type}/{hasData}", method = RequestMethod.GET) @ResponseBody - public PageData getFirmwares(@PathVariable("hasData") boolean hasData, + public PageData getFirmwares(@PathVariable("deviceProfileId") String strDeviceProfileId, + @PathVariable("type") String strType, + @PathVariable("hasData") boolean hasData, @RequestParam int pageSize, @RequestParam int page, @RequestParam(required = false) String textSearch, @RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortOrder) throws ThingsboardException { + checkParameter("deviceProfileId", strDeviceProfileId); + checkParameter("type", strType); try { PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); - return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndHasData(getTenantId(), hasData, pageLink)); + return checkNotNull(firmwareService.findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(getTenantId(), + new DeviceProfileId(toUUID(strDeviceProfileId)), FirmwareType.valueOf(strType), hasData, pageLink)); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index 8638805d81..c4ad9feceb 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.firmware; import com.google.common.util.concurrent.FutureCallback; -import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; @@ -45,6 +44,7 @@ import org.thingsboard.server.dao.firmware.FirmwareService; import org.thingsboard.server.gen.transport.TransportProtos.ToFirmwareStateServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.queue.TbClusterService; @@ -71,7 +71,6 @@ import static org.thingsboard.server.common.data.firmware.FirmwareKeyUtil.getTel @Slf4j @Service @TbCoreComponent -@RequiredArgsConstructor public class DefaultFirmwareStateService implements FirmwareStateService { private final TbClusterService tbClusterService; @@ -81,6 +80,19 @@ public class DefaultFirmwareStateService implements FirmwareStateService { private final RuleEngineTelemetryService telemetryService; private final TbQueueProducer> fwStateMsgProducer; + public DefaultFirmwareStateService(TbClusterService tbClusterService, FirmwareService firmwareService, + DeviceService deviceService, + DeviceProfileService deviceProfileService, + RuleEngineTelemetryService telemetryService, + TbCoreQueueFactory coreQueueFactory) { + this.tbClusterService = tbClusterService; + this.firmwareService = firmwareService; + this.deviceService = deviceService; + this.deviceProfileService = deviceProfileService; + this.telemetryService = telemetryService; + this.fwStateMsgProducer = coreQueueFactory.createToFirmwareStateServiceMsgProducer(); + } + @Override public void update(Device device, Device oldDevice) { FirmwareId newFirmwareId = device.getFirmwareId(); 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 025122a01b..463af51862 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 @@ -480,6 +480,7 @@ public class DefaultTransportApiService implements TransportApiService { builder.setResponseStatus(TransportProtos.ResponseStatus.SUCCESS); builder.setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits()); builder.setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()); + builder.setType(firmwareInfo.getType().name()); builder.setTitle(firmwareInfo.getTitle()); builder.setVersion(firmwareInfo.getVersion()); builder.setFileName(firmwareInfo.getFileName()); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java index 425a21e1a2..ec36be13a5 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseFirmwareControllerTest.java @@ -24,10 +24,13 @@ import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; @@ -38,6 +41,7 @@ import java.util.Collections; import java.util.List; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; public abstract class BaseFirmwareControllerTest extends AbstractControllerTest { @@ -53,6 +57,7 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest private Tenant savedTenant; private User tenantAdmin; + private DeviceProfileId deviceProfileId; @Before public void beforeTest() throws Exception { @@ -71,6 +76,11 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest tenantAdmin.setLastName("Downs"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + + DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", null); + DeviceProfile savedDeviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertNotNull(savedDeviceProfile); + deviceProfileId = savedDeviceProfile.getId(); } @After @@ -84,6 +94,8 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @Test public void testSaveFirmware() throws Exception { FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); @@ -107,6 +119,8 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @Test public void testSaveFirmwareData() throws Exception { FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); @@ -137,6 +151,8 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @Test public void testUpdateFirmwareFromDifferentTenant() throws Exception { FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); @@ -150,6 +166,8 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @Test public void testFindFirmwareInfoById() throws Exception { FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); @@ -163,6 +181,8 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @Test public void testFindFirmwareById() throws Exception { FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); @@ -180,6 +200,8 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest @Test public void testDeleteFirmware() throws Exception { FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); @@ -197,6 +219,8 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest List firmwares = new ArrayList<>(); for (int i = 0; i < 165; i++) { FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION + i); @@ -238,6 +262,8 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest for (int i = 0; i < 165; i++) { FirmwareInfo firmwareInfo = new FirmwareInfo(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION + i); @@ -257,7 +283,7 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest PageLink pageLink = new PageLink(24); PageData pageData; do { - pageData = doGetTypedWithPageLink("/api/firmwares/true?", + pageData = doGetTypedWithPageLink("/api/firmwares/" + deviceProfileId.toString() + "/FIRMWARE/true?", new TypeReference<>() { }, pageLink); loadedFirmwaresWithData.addAll(pageData.getData()); @@ -269,7 +295,7 @@ public abstract class BaseFirmwareControllerTest extends AbstractControllerTest List loadedFirmwaresWithoutData = new ArrayList<>(); pageLink = new PageLink(24); do { - pageData = doGetTypedWithPageLink("/api/firmwares/false?", + pageData = doGetTypedWithPageLink("/api/firmwares/" + deviceProfileId.toString() + "/FIRMWARE/false?", new TypeReference<>() { }, pageLink); loadedFirmwaresWithoutData.addAll(pageData.getData()); 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 653f6978f6..85b058f597 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -29,7 +29,7 @@ import java.util.Arrays; // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", // "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest", // "org.thingsboard.server.controller.sql.TbResourceControllerSqlTest", - "org.thingsboard.server.controller.sql.*Test", + "org.thingsboard.server.controller.sql.FirmwareControllerSqlTest", }) public class ControllerSqlTestSuite { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java index 907638eabb..980f8303f2 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/firmware/FirmwareService.java @@ -18,6 +18,8 @@ package org.thingsboard.server.dao.firmware; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -37,7 +39,7 @@ public interface FirmwareService { PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink); - PageData findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink); + PageData findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink); void deleteFirmware(TenantId tenantId, FirmwareId firmwareId); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index 5282435c0a..b48fc114cb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -50,6 +50,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen private byte[] deviceDataBytes; private FirmwareId firmwareId; + private FirmwareId softwareId; public Device() { super(); @@ -69,6 +70,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen this.deviceProfileId = device.getDeviceProfileId(); this.setDeviceData(device.getDeviceData()); this.firmwareId = device.getFirmwareId(); + this.softwareId = device.getSoftwareId(); } public Device updateDevice(Device device) { @@ -79,6 +81,8 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen this.label = device.getLabel(); this.deviceProfileId = device.getDeviceProfileId(); this.setDeviceData(device.getDeviceData()); + this.setFirmwareId(device.getFirmwareId()); + this.setSoftwareId(device.getSoftwareId()); return this; } @@ -171,6 +175,14 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen this.firmwareId = firmwareId; } + public FirmwareId getSoftwareId() { + return softwareId; + } + + public void setSoftwareId(FirmwareId softwareId) { + this.softwareId = softwareId; + } + @Override public String toString() { StringBuilder builder = new StringBuilder(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 660d9dec93..c3a2e5ecee 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -59,6 +59,8 @@ public class DeviceProfile extends SearchTextBased implements H private FirmwareId firmwareId; + private FirmwareId softwareId; + public DeviceProfile() { super(); } @@ -77,6 +79,8 @@ public class DeviceProfile extends SearchTextBased implements H this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.setProfileData(deviceProfile.getProfileData()); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); + this.firmwareId = deviceProfile.getFirmwareId(); + this.softwareId = deviceProfile.getSoftwareId(); } @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java index aee81bcde8..9b00e9b256 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/FirmwareInfo.java @@ -20,6 +20,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,6 +32,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo private static final long serialVersionUID = 3168391583570815419L; private TenantId tenantId; + private DeviceProfileId deviceProfileId; private FirmwareType type; private String title; private String version; @@ -53,6 +55,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo public FirmwareInfo(FirmwareInfo firmwareInfo) { super(firmwareInfo); this.tenantId = firmwareInfo.getTenantId(); + this.deviceProfileId = firmwareInfo.getDeviceProfileId(); this.type = firmwareInfo.getType(); this.title = firmwareInfo.getTitle(); this.version = firmwareInfo.getVersion(); 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 7833c80ed5..020fd7c524 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 @@ -409,6 +409,16 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D throw new DataValidationException("Can't assign firmware with empty data!"); } } + + if (deviceProfile.getSoftwareId() != null) { + Firmware software = firmwareService.findFirmwareById(tenantId, deviceProfile.getSoftwareId()); + if (software == null) { + throw new DataValidationException("Can't assign non-existent software!"); + } + if (software.getData() == null) { + throw new DataValidationException("Can't assign software with empty data!"); + } + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 73dadef589..dd9b46b62f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -686,6 +686,22 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe if (firmware.getData() == null) { throw new DataValidationException("Can't assign firmware with empty data!"); } + if (!firmware.getDeviceProfileId().equals(device.getDeviceProfileId())) { + throw new DataValidationException("Can't assign firmware with different deviceProfile!"); + } + } + + if (device.getSoftwareId() != null) { + Firmware software = firmwareService.findFirmwareById(tenantId, device.getSoftwareId()); + if (software == null) { + throw new DataValidationException("Can't assign non-existent software!"); + } + if (software.getData() == null) { + throw new DataValidationException("Can't assign software with empty data!"); + } + if (!software.getDeviceProfileId().equals(device.getDeviceProfileId())) { + throw new DataValidationException("Can't assign firmware with different deviceProfile!"); + } } } }; diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 5876947440..207d629bbe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -27,13 +27,17 @@ import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import org.thingsboard.server.cache.firmware.FirmwareDataCache; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -56,6 +60,7 @@ public class BaseFirmwareService implements FirmwareService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private final TenantDao tenantDao; + private final DeviceProfileDao deviceProfileDao; private final FirmwareDao firmwareDao; private final FirmwareInfoDao firmwareInfoDao; private final CacheManager cacheManager; @@ -124,7 +129,8 @@ public class BaseFirmwareService implements FirmwareService { public ListenableFuture findFirmwareInfoByIdAsync(TenantId tenantId, FirmwareId firmwareId) { log.trace("Executing findFirmwareInfoByIdAsync [{}]", firmwareId); validateId(firmwareId, INCORRECT_FIRMWARE_ID + firmwareId); - return firmwareInfoDao.findByIdAsync(tenantId, firmwareId.getId()); } + return firmwareInfoDao.findByIdAsync(tenantId, firmwareId.getId()); + } @Override public PageData findTenantFirmwaresByTenantId(TenantId tenantId, PageLink pageLink) { @@ -135,11 +141,11 @@ public class BaseFirmwareService implements FirmwareService { } @Override - public PageData findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) { + public PageData findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink) { log.trace("Executing findTenantFirmwaresByTenantIdAndHasData, tenantId [{}], hasData [{}] pageLink [{}]", tenantId, hasData, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validatePageLink(pageLink); - return firmwareInfoDao.findFirmwareInfoByTenantIdAndHasData(tenantId, hasData, pageLink); + return firmwareInfoDao.findFirmwareInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, firmwareType, hasData, pageLink); } @Override @@ -157,6 +163,10 @@ public class BaseFirmwareService implements FirmwareService { throw new DataValidationException("The firmware referenced by the devices cannot be deleted!"); } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_firmware_device_profile")) { throw new DataValidationException("The firmware referenced by the device profile cannot be deleted!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_software_device")) { + throw new DataValidationException("The software referenced by the devices cannot be deleted!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_software_device_profile")) { + throw new DataValidationException("The software referenced by the device profile cannot be deleted!"); } else { throw t; } @@ -173,23 +183,8 @@ public class BaseFirmwareService implements FirmwareService { private DataValidator firmwareInfoValidator = new DataValidator<>() { @Override - protected void validateDataImpl(TenantId tenantId, FirmwareInfo firmware) { - if (firmware.getTenantId() == null) { - throw new DataValidationException("Firmware should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Firmware is referencing to non-existent tenant!"); - } - } - - if (StringUtils.isEmpty(firmware.getTitle())) { - throw new DataValidationException("Firmware title should be specified!"); - } - - if (StringUtils.isEmpty(firmware.getVersion())) { - throw new DataValidationException("Firmware version should be specified!"); - } + protected void validateDataImpl(TenantId tenantId, FirmwareInfo firmwareInfo) { + validateImpl(firmwareInfo); } @Override @@ -204,22 +199,7 @@ public class BaseFirmwareService implements FirmwareService { @Override protected void validateDataImpl(TenantId tenantId, Firmware firmware) { - if (firmware.getTenantId() == null) { - throw new DataValidationException("Firmware should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(firmware.getTenantId(), firmware.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Firmware is referencing to non-existent tenant!"); - } - } - - if (StringUtils.isEmpty(firmware.getTitle())) { - throw new DataValidationException("Firmware title should be specified!"); - } - - if (StringUtils.isEmpty(firmware.getVersion())) { - throw new DataValidationException("Firmware version should be specified!"); - } + validateImpl(firmware); if (StringUtils.isEmpty(firmware.getFileName())) { throw new DataValidationException("Firmware file name should be specified!"); @@ -276,6 +256,14 @@ public class BaseFirmwareService implements FirmwareService { }; private static void validateUpdate(FirmwareInfo firmware, FirmwareInfo firmwareOld) { + if (!firmwareOld.getDeviceProfileId().equals(firmware.getDeviceProfileId())) { + throw new DataValidationException("Updating firmware deviceProfile is prohibited!"); + } + + if (!firmwareOld.getType().equals(firmware.getType())) { + throw new DataValidationException("Updating type is prohibited!"); + } + if (!firmwareOld.getTitle().equals(firmware.getTitle())) { throw new DataValidationException("Updating firmware title is prohibited!"); } @@ -305,6 +293,38 @@ public class BaseFirmwareService implements FirmwareService { } } + private void validateImpl(FirmwareInfo firmwareInfo) { + if (firmwareInfo.getTenantId() == null) { + throw new DataValidationException("Firmware should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(firmwareInfo.getTenantId(), firmwareInfo.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Firmware is referencing to non-existent tenant!"); + } + } + + if (firmwareInfo.getDeviceProfileId() == null) { + throw new DataValidationException("Firmware should be assigned to deviceProfile!"); + } else { + DeviceProfile deviceProfile = deviceProfileDao.findById(firmwareInfo.getTenantId(), firmwareInfo.getDeviceProfileId().getId()); + if (deviceProfile == null) { + throw new DataValidationException("Firmware is referencing to non-existent device profile!"); + } + } + + if (firmwareInfo.getType() == null) { + throw new DataValidationException("Type should be specified!"); + } + + if (StringUtils.isEmpty(firmwareInfo.getTitle())) { + throw new DataValidationException("Firmware title should be specified!"); + } + + if (StringUtils.isEmpty(firmwareInfo.getVersion())) { + throw new DataValidationException("Firmware version should be specified!"); + } + } + private PaginatedRemover tenantFirmwareRemover = new PaginatedRemover<>() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java index a2af06ad9a..32ca8dc528 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java @@ -16,6 +16,8 @@ package org.thingsboard.server.dao.firmware; import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -27,6 +29,6 @@ public interface FirmwareInfoDao extends Dao { PageData findFirmwareInfoByTenantId(TenantId tenantId, PageLink pageLink); - PageData findFirmwareInfoByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink); + PageData findFirmwareInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink); } 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 260c8aa743..bc0afe630d 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 @@ -154,6 +154,7 @@ public class ModelConstants { public static final String DEVICE_DEVICE_PROFILE_ID_PROPERTY = "device_profile_id"; public static final String DEVICE_DEVICE_DATA_PROPERTY = "device_data"; public static final String DEVICE_FIRMWARE_ID_PROPERTY = "firmware_id"; + public static final String DEVICE_SOFTWARE_ID_PROPERTY = "software_id"; public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text"; public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text"; @@ -178,6 +179,7 @@ public class ModelConstants { public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name"; public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; public static final String DEVICE_PROFILE_FIRMWARE_ID_PROPERTY = "firmware_id"; + public static final String DEVICE_PROFILE_SOFTWARE_ID_PROPERTY = "software_id"; /** * Cassandra entityView constants. @@ -476,6 +478,7 @@ public class ModelConstants { */ public static final String FIRMWARE_TABLE_NAME = "firmware"; public static final String FIRMWARE_TENANT_ID_COLUMN = TENANT_ID_COLUMN; + public static final String FIRMWARE_DEVICE_PROFILE_ID_COLUMN = "device_profile_id"; public static final String FIRMWARE_TYPE_COLUMN = "type"; public static final String FIRMWARE_TITLE_COLUMN = TITLE_PROPERTY; public static final String FIRMWARE_VERSION_COLUMN = "version"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index 8dee576611..5b9d49a036 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -22,6 +22,7 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.hibernate.annotations.TypeDefs; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.id.CustomerId; @@ -32,7 +33,6 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JsonBinaryType; import org.thingsboard.server.dao.util.mapping.JsonStringType; @@ -43,8 +43,8 @@ import java.util.UUID; @Data @EqualsAndHashCode(callSuper = true) @TypeDefs({ - @TypeDef(name = "json", typeClass = JsonStringType.class), - @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) + @TypeDef(name = "json", typeClass = JsonStringType.class), + @TypeDef(name = "jsonb", typeClass = JsonBinaryType.class) }) @MappedSuperclass public abstract class AbstractDeviceEntity extends BaseSqlEntity implements SearchTextEntity { @@ -77,6 +77,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti @Column(name = ModelConstants.DEVICE_FIRMWARE_ID_PROPERTY, columnDefinition = "uuid") private UUID firmwareId; + @Column(name = ModelConstants.DEVICE_SOFTWARE_ID_PROPERTY, columnDefinition = "uuid") + private UUID softwareId; + @Type(type = "jsonb") @Column(name = ModelConstants.DEVICE_DEVICE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode deviceData; @@ -102,6 +105,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti if (device.getFirmwareId() != null) { this.firmwareId = device.getFirmwareId().getId(); } + if (device.getSoftwareId() != null) { + this.firmwareId = device.getSoftwareId().getId(); + } this.deviceData = JacksonUtil.convertValue(device.getDeviceData(), ObjectNode.class); this.name = device.getName(); this.type = device.getType(); @@ -122,6 +128,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti this.searchText = deviceEntity.getSearchText(); this.additionalInfo = deviceEntity.getAdditionalInfo(); this.firmwareId = deviceEntity.getFirmwareId(); + this.softwareId = deviceEntity.getSoftwareId(); } @Override @@ -149,6 +156,9 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti if (firmwareId != null) { device.setFirmwareId(new FirmwareId(firmwareId)); } + if (softwareId != null) { + device.setSoftwareId(new FirmwareId(softwareId)); + } device.setDeviceData(JacksonUtil.convertValue(deviceData, DeviceData.class)); device.setName(name); device.setType(type); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index e437c87fdd..e4c32a44f0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -21,9 +21,10 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceProfileType; 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.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -33,7 +34,6 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JsonBinaryType; import javax.persistence.Column; @@ -87,12 +87,15 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode profileData; - @Column(name=ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY) + @Column(name = ModelConstants.DEVICE_PROFILE_PROVISION_DEVICE_KEY) private String provisionDeviceKey; - @Column(name=ModelConstants.DEVICE_PROFILE_FIRMWARE_ID_PROPERTY) + @Column(name = ModelConstants.DEVICE_PROFILE_FIRMWARE_ID_PROPERTY) private UUID firmwareId; + @Column(name = ModelConstants.DEVICE_PROFILE_SOFTWARE_ID_PROPERTY) + private UUID softwareId; + public DeviceProfileEntity() { super(); } @@ -120,6 +123,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (deviceProfile.getFirmwareId() != null) { this.firmwareId = deviceProfile.getFirmwareId().getId(); } + if (deviceProfile.getSoftwareId() != null) { + this.firmwareId = deviceProfile.getSoftwareId().getId(); + } } @Override @@ -160,6 +166,10 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl deviceProfile.setFirmwareId(new FirmwareId(firmwareId)); } + if (softwareId != null) { + deviceProfile.setSoftwareId(new FirmwareId(softwareId)); + } + return deviceProfile; } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java index 6dfa2e893e..a10ec8f7ed 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -22,6 +22,7 @@ import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -43,6 +44,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_SIZE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DEVICE_PROFILE_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; @@ -61,6 +63,9 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex @Column(name = FIRMWARE_TENANT_ID_COLUMN) private UUID tenantId; + @Column(name = FIRMWARE_DEVICE_PROFILE_ID_COLUMN) + private UUID deviceProfileId; + @Enumerated(EnumType.STRING) @Column(name = FIRMWARE_TYPE_COLUMN) private FirmwareType type; @@ -105,6 +110,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex this.createdTime = firmware.getCreatedTime(); this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); + this.deviceProfileId = firmware.getDeviceProfileId().getId(); this.type = firmware.getType(); this.title = firmware.getTitle(); this.version = firmware.getVersion(); @@ -132,6 +138,7 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex Firmware firmware = new Firmware(new FirmwareId(id)); firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); + firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); firmware.setType(type); firmware.setTitle(title); firmware.setVersion(version); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java index 067b061396..b1e058a012 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java @@ -23,6 +23,7 @@ import org.hibernate.annotations.TypeDef; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseSqlEntity; @@ -42,6 +43,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_ import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CHECKSUM_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_CONTENT_TYPE_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DATA_SIZE_COLUMN; +import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_DEVICE_PROFILE_ID_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_FILE_NAME_COLUMN; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TABLE_NAME; import static org.thingsboard.server.dao.model.ModelConstants.FIRMWARE_TENANT_ID_COLUMN; @@ -60,6 +62,9 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Column(name = FIRMWARE_TENANT_ID_COLUMN) private UUID tenantId; + @Column(name = FIRMWARE_DEVICE_PROFILE_ID_COLUMN) + private UUID deviceProfileId; + @Enumerated(EnumType.STRING) @Column(name = FIRMWARE_TYPE_COLUMN) private FirmwareType type; @@ -103,6 +108,8 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.createdTime = firmware.getCreatedTime(); this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); + this.type = firmware.getType(); + this.deviceProfileId = firmware.getDeviceProfileId().getId(); this.title = firmware.getTitle(); this.version = firmware.getVersion(); this.fileName = firmware.getFileName(); @@ -113,13 +120,14 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.additionalInfo = firmware.getAdditionalInfo(); } - public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String type, String title, String version, + public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, UUID deviceProfileId, FirmwareType type, String title, String version, String fileName, String contentType, String checksumAlgorithm, String checksum, Long dataSize, Object additionalInfo, boolean hasData) { this.id = id; this.createdTime = createdTime; this.tenantId = tenantId; - this.type = FirmwareType.valueOf(type); + this.deviceProfileId = deviceProfileId; + this.type = type; this.title = title; this.version = version; this.fileName = fileName; @@ -146,6 +154,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S FirmwareInfo firmware = new FirmwareInfo(new FirmwareId(id)); firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); + firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); firmware.setType(type); firmware.setTitle(title); firmware.setVersion(version); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java index 458cecf63d..5a7af9997b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java @@ -20,27 +20,32 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.dao.model.sql.FirmwareInfoEntity; import java.util.UUID; public interface FirmwareInfoRepository extends CrudRepository { - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + "f.tenantId = :tenantId " + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") Page findAllByTenantId(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE " + "f.tenantId = :tenantId " + + "AND f.deviceProfileId = :deviceProfileId " + + "AND f.type = :type " + "AND ((f.data IS NOT NULL AND :hasData = true) OR (f.data IS NULL AND :hasData = false ))" + "AND LOWER(f.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") - Page findAllByTenantIdAndHasData(@Param("tenantId") UUID tenantId, - @Param("hasData") boolean hasData, - @Param("searchText") String searchText, - Pageable pageable); + Page findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData(@Param("tenantId") UUID tenantId, + @Param("deviceProfileId") UUID deviceProfileId, + @Param("type") FirmwareType type, + @Param("hasData") boolean hasData, + @Param("searchText") String searchText, + Pageable pageable); - @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE f.id = :id") + @Query("SELECT new FirmwareInfoEntity(f.id, f.createdTime, f.tenantId, f.deviceProfileId, f.type, f.title, f.version, f.fileName, f.contentType, f.checksumAlgorithm, f.checksum, f.dataSize, f.additionalInfo, f.data IS NOT NULL) FROM FirmwareEntity f WHERE f.id = :id") FirmwareInfoEntity findFirmwareInfoById(@Param("id") UUID id); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java index 3b80c1c7e5..db7fb37ffe 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/JpaFirmwareInfoDao.java @@ -20,6 +20,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.FirmwareInfo; +import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -73,10 +75,12 @@ public class JpaFirmwareInfoDao extends JpaAbstractSearchTextDao findFirmwareInfoByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) { + public PageData findFirmwareInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink) { return DaoUtil.toPageData(firmwareInfoRepository - .findAllByTenantIdAndHasData( + .findAllByTenantIdAndTypeAndDeviceProfileIdAndHasData( tenantId.getId(), + deviceProfileId.getId(), + firmwareType, hasData, Objects.toString(pageLink.getTextSearch(), ""), DaoUtil.toPageable(pageLink))); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index d9bc1003dc..89dcf44f35 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -162,6 +162,7 @@ CREATE TABLE IF NOT EXISTS firmware ( id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid NOT NULL, + device_profile_id uuid NOT NULL, type varchar(32) NOT NULL, title varchar(255) NOT NULL, version varchar(255) NOT NULL, @@ -189,13 +190,15 @@ CREATE TABLE IF NOT EXISTS device_profile ( is_default boolean, tenant_id uuid, firmware_id uuid, + software_id uuid, default_rule_chain_id uuid, default_queue_name varchar(255), provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), - CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id) + CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id), + CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES firmware(id) ); CREATE TABLE IF NOT EXISTS device ( @@ -211,9 +214,11 @@ CREATE TABLE IF NOT EXISTS device ( search_text varchar(255), tenant_id uuid, firmware_id uuid, + software_id uuid, CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id), - CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id) + CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id), + CONSTRAINT fk_software_device FOREIGN KEY (software_id) REFERENCES firmware(id) ); CREATE TABLE IF NOT EXISTS device_credentials ( diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index a5373fbaaf..245759cd22 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -192,8 +192,8 @@ CREATE TABLE IF NOT EXISTS firmware ( data_size bigint, additional_info varchar, search_text varchar(255), - CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version), - CONSTRAINT fk_firmware_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id) ON DELETE CASCADE + CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) +-- CONSTRAINT fk_device_profile_firmware FOREIGN KEY (device_profile_id) REFERENCES device_profile(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS device_profile ( @@ -216,8 +216,8 @@ CREATE TABLE IF NOT EXISTS device_profile ( CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id), - CONSTRAINT fk_device_profile_firmware FOREIGN KEY (firmware_id) REFERENCES firmware(id) - CONSTRAINT fk_device_profile_software FOREIGN KEY (software_id) REFERENCES firmware(id) + CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES firmware(id), + CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES firmware(id) ); -- We will use one-to-many relation in the first release and extend this feature in case of user requests @@ -245,7 +245,7 @@ CREATE TABLE IF NOT EXISTS device ( software_id uuid, CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id), - CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id) + CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id), CONSTRAINT fk_software_device FOREIGN KEY (software_id) REFERENCES firmware(id) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java index e377baa2cb..618135fdd3 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java @@ -30,6 +30,7 @@ import org.thingsboard.server.common.data.DeviceProfileInfo; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -43,6 +44,8 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.stream.Collectors; +import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; + public class BaseDeviceProfileServiceTest extends AbstractServiceTest { private IdComparator idComparator = new IdComparator<>(); @@ -97,6 +100,8 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(savedDeviceProfile.getId()); + firmware.setType(FIRMWARE); firmware.setTitle("my firmware"); firmware.setVersion("v1.0"); firmware.setFileName("test.txt"); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java index 6d7b695ef1..4fcdd93bce 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceServiceTest.java @@ -20,10 +20,13 @@ import org.apache.commons.lang3.RandomStringUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceInfo; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.Tenant; @@ -42,6 +45,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; public abstract class BaseDeviceServiceTest extends AbstractServiceTest { @@ -66,6 +70,9 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { tenantProfileService.deleteTenantProfiles(anotherTenantId); } + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Test public void testSaveDevicesWithoutMaxDeviceLimit() { Device device = this.saveDevice(tenantId, "My device"); @@ -183,6 +190,8 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(device.getDeviceProfileId()); + firmware.setType(FIRMWARE); firmware.setTitle("my firmware"); firmware.setVersion("v1.0"); firmware.setFileName("test.txt"); @@ -198,6 +207,40 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { Device foundDevice = deviceService.findDeviceById(tenantId, savedDevice.getId()); Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); } + + @Test + public void testAssignFirmwareToDeviceWithDifferentDeviceProfile() { + Device device = new Device(); + device.setTenantId(tenantId); + device.setName("My device"); + device.setType("default"); + Device savedDevice = deviceService.saveDevice(device); + + Assert.assertNotNull(savedDevice); + + DeviceProfile deviceProfile = createDeviceProfile(tenantId, "New device Profile"); + DeviceProfile savedProfile = deviceProfileService.saveDeviceProfile(deviceProfile); + Assert.assertNotNull(savedProfile); + + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(savedProfile.getId()); + firmware.setType(FIRMWARE); + firmware.setTitle("my firmware"); + firmware.setVersion("v1.0"); + firmware.setFileName("test.txt"); + firmware.setContentType("text/plain"); + firmware.setChecksumAlgorithm("sha256"); + firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); + firmware.setData(ByteBuffer.wrap(new byte[]{1})); + Firmware savedFirmware = firmwareService.saveFirmware(firmware); + + savedDevice.setFirmwareId(savedFirmware.getId()); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Can't assign firmware with different deviceProfile!"); + deviceService.saveDevice(savedDevice); + } @Test(expected = DataValidationException.class) public void testSaveDeviceWithEmptyName() { diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java index bf96d09922..816b59fb64 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseFirmwareServiceTest.java @@ -19,13 +19,16 @@ import com.datastax.oss.driver.api.core.uuid.Uuids; import org.junit.After; import org.junit.Assert; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -36,6 +39,8 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; + public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public static final String TITLE = "My firmware"; @@ -50,6 +55,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { private TenantId tenantId; + private DeviceProfileId deviceProfileId; + @Before public void before() { Tenant tenant = new Tenant(); @@ -57,8 +64,16 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Tenant savedTenant = tenantService.saveTenant(tenant); Assert.assertNotNull(savedTenant); tenantId = savedTenant.getId(); + + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); + DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); + Assert.assertNotNull(savedDeviceProfile); + deviceProfileId = savedDeviceProfile.getId(); } + @Rule + public ExpectedException thrown = ExpectedException.none(); + @After public void after() { tenantService.deleteTenant(tenantId); @@ -68,6 +83,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public void testSaveFirmware() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -99,6 +116,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public void testSaveFirmwareInfoAndUpdateWithData() { FirmwareInfo firmwareInfo = new FirmwareInfo(); firmwareInfo.setTenantId(tenantId); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); FirmwareInfo savedFirmwareInfo = firmwareService.saveFirmwareInfo(firmwareInfo); @@ -112,6 +131,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(savedFirmwareInfo.getId()); firmware.setCreatedTime(firmwareInfo.getCreatedTime()); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -135,9 +156,11 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmwareService.deleteFirmware(tenantId, savedFirmwareInfo.getId()); } - @Test(expected = DataValidationException.class) + @Test public void testSaveFirmwareWithEmptyTenant() { Firmware firmware = new Firmware(); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -145,65 +168,126 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware should be assigned to tenant!"); firmwareService.saveFirmware(firmware); } - @Test(expected = DataValidationException.class) + @Test + public void testSaveFirmwareWithEmptyDeviceProfile() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setType(FIRMWARE); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); + firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware should be assigned to deviceProfile!"); + firmwareService.saveFirmware(firmware); + } + + @Test + public void testSaveFirmwareWithEmptyType() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); + firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Type should be specified!"); + firmwareService.saveFirmware(firmware); + } + + @Test public void testSaveFirmwareWithEmptyTitle() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware title should be specified!"); firmwareService.saveFirmware(firmware); } - @Test(expected = DataValidationException.class) + @Test public void testSaveFirmwareWithEmptyFileName() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setContentType(CONTENT_TYPE); firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware file name should be specified!"); firmwareService.saveFirmware(firmware); } - @Test(expected = DataValidationException.class) + @Test public void testSaveFirmwareWithEmptyContentType() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware content type should be specified!"); firmwareService.saveFirmware(firmware); } - @Test(expected = DataValidationException.class) + @Test public void testSaveFirmwareWithEmptyData() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware data should be specified!"); firmwareService.saveFirmware(firmware); } - @Test(expected = DataValidationException.class) + @Test public void testSaveFirmwareWithInvalidTenant() { Firmware firmware = new Firmware(); firmware.setTenantId(new TenantId(Uuids.timeBased())); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -211,41 +295,77 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setChecksum(CHECKSUM); firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware is referencing to non-existent tenant!"); firmwareService.saveFirmware(firmware); } - @Test(expected = DataValidationException.class) + @Test + public void testSaveFirmwareWithInvalidDeviceProfileId() { + Firmware firmware = new Firmware(); + firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(new DeviceProfileId(Uuids.timeBased())); + firmware.setType(FIRMWARE); + firmware.setTitle(TITLE); + firmware.setVersion(VERSION); + firmware.setFileName(FILE_NAME); + firmware.setContentType(CONTENT_TYPE); + firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + firmware.setChecksum(CHECKSUM); + firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware is referencing to non-existent device profile!"); + firmwareService.saveFirmware(firmware); + } + + @Test public void testSaveFirmwareWithEmptyChecksum() { Firmware firmware = new Firmware(); - firmware.setTenantId(new TenantId(Uuids.timeBased())); + firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); firmware.setContentType(CONTENT_TYPE); firmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); firmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware checksum should be specified!"); firmwareService.saveFirmware(firmware); } - @Test(expected = DataValidationException.class) + @Test public void testSaveFirmwareInfoWithExistingTitleAndVersion() { FirmwareInfo firmwareInfo = new FirmwareInfo(); firmwareInfo.setTenantId(tenantId); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION); firmwareService.saveFirmwareInfo(firmwareInfo); FirmwareInfo newFirmwareInfo = new FirmwareInfo(); newFirmwareInfo.setTenantId(tenantId); + newFirmwareInfo.setDeviceProfileId(deviceProfileId); + newFirmwareInfo.setType(FIRMWARE); newFirmwareInfo.setTitle(TITLE); newFirmwareInfo.setVersion(VERSION); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware with such title and version already exists!"); firmwareService.saveFirmwareInfo(newFirmwareInfo); } - @Test(expected = DataValidationException.class) + @Test public void testSaveFirmwareWithExistingTitleAndVersion() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -257,18 +377,27 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware newFirmware = new Firmware(); newFirmware.setTenantId(tenantId); + newFirmware.setDeviceProfileId(deviceProfileId); + newFirmware.setType(FIRMWARE); newFirmware.setTitle(TITLE); newFirmware.setVersion(VERSION); newFirmware.setFileName(FILE_NAME); newFirmware.setContentType(CONTENT_TYPE); + newFirmware.setChecksumAlgorithm(CHECKSUM_ALGORITHM); + newFirmware.setChecksum(CHECKSUM); newFirmware.setData(DATA); + + thrown.expect(DataValidationException.class); + thrown.expectMessage("Firmware with such title and version already exists!"); firmwareService.saveFirmware(newFirmware); } - @Test(expected = DataValidationException.class) + @Test public void testDeleteFirmwareWithReferenceByDevice() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -281,11 +410,13 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Device device = new Device(); device.setTenantId(tenantId); device.setName("My device"); - device.setType("default"); + device.setDeviceProfileId(deviceProfileId); device.setFirmwareId(savedFirmware.getId()); Device savedDevice = deviceService.saveDevice(device); try { + thrown.expect(DataValidationException.class); + thrown.expectMessage("The firmware referenced by the devices cannot be deleted!"); firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); } finally { deviceService.deleteDevice(tenantId, savedDevice.getId()); @@ -293,10 +424,15 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { } } - @Test(expected = DataValidationException.class) + @Test public void testDeleteFirmwareWithReferenceByDeviceProfile() { + DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Test Device Profile"); + DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); + Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(savedDeviceProfile.getId()); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -306,11 +442,12 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { firmware.setData(DATA); Firmware savedFirmware = firmwareService.saveFirmware(firmware); - DeviceProfile deviceProfile = this.createDeviceProfile(tenantId, "Device Profile"); - deviceProfile.setFirmwareId(savedFirmware.getId()); - DeviceProfile savedDeviceProfile = deviceProfileService.saveDeviceProfile(deviceProfile); + savedDeviceProfile.setFirmwareId(savedFirmware.getId()); + deviceProfileService.saveDeviceProfile(savedDeviceProfile); try { + thrown.expect(DataValidationException.class); + thrown.expectMessage("The firmware referenced by the device profile cannot be deleted!"); firmwareService.deleteFirmware(tenantId, savedFirmware.getId()); } finally { deviceProfileService.deleteDeviceProfile(tenantId, savedDeviceProfile.getId()); @@ -322,6 +459,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public void testFindFirmwareById() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -341,6 +480,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public void testFindFirmwareInfoById() { FirmwareInfo firmware = new FirmwareInfo(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); FirmwareInfo savedFirmware = firmwareService.saveFirmwareInfo(firmware); @@ -355,6 +496,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { public void testDeleteFirmware() { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION); firmware.setFileName(FILE_NAME); @@ -377,6 +520,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { for (int i = 0; i < 165; i++) { Firmware firmware = new Firmware(); firmware.setTenantId(tenantId); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(TITLE); firmware.setVersion(VERSION + i); firmware.setFileName(FILE_NAME); @@ -420,6 +565,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { for (int i = 0; i < 165; i++) { FirmwareInfo firmwareInfo = new FirmwareInfo(); firmwareInfo.setTenantId(tenantId); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); firmwareInfo.setTitle(TITLE); firmwareInfo.setVersion(VERSION + i); firmwareInfo.setFileName(FILE_NAME); @@ -434,7 +581,7 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { PageLink pageLink = new PageLink(16); PageData pageData; do { - pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, false, pageLink); + pageData = firmwareService.findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, false, pageLink); loadedFirmwares.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -450,6 +597,8 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { Firmware firmware = new Firmware(f.getId()); firmware.setCreatedTime(f.getCreatedTime()); firmware.setTenantId(f.getTenantId()); + firmware.setDeviceProfileId(deviceProfileId); + firmware.setType(FIRMWARE); firmware.setTitle(f.getTitle()); firmware.setVersion(f.getVersion()); firmware.setFileName(FILE_NAME); @@ -465,7 +614,7 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { loadedFirmwares = new ArrayList<>(); pageLink = new PageLink(16); do { - pageData = firmwareService.findTenantFirmwaresByTenantIdAndHasData(tenantId, true, pageLink); + pageData = firmwareService.findTenantFirmwaresByTenantIdAndDeviceProfileIdAndTypeAndHasData(tenantId, deviceProfileId, FIRMWARE, true, pageLink); loadedFirmwares.addAll(pageData.getData()); if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); From ca2e08f8490e642dbd361be1a69c1fd2ef730c7f Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 29 Apr 2021 10:10:21 +0300 Subject: [PATCH 111/127] refactoring --- .../thingsboard/server/controller/ControllerSqlTestSuite.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 85b058f597..653f6978f6 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -29,7 +29,7 @@ import java.util.Arrays; // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", // "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest", // "org.thingsboard.server.controller.sql.TbResourceControllerSqlTest", - "org.thingsboard.server.controller.sql.FirmwareControllerSqlTest", + "org.thingsboard.server.controller.sql.*Test", }) public class ControllerSqlTestSuite { From fd8dee0a9d5252d6d65d8bfeef906bf401e3ee61 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 29 Apr 2021 16:01:47 +0300 Subject: [PATCH 112/127] UI: Add firmware type --- ui-ngx/src/app/core/http/entity.service.ts | 2 +- ui-ngx/src/app/core/http/firmware.service.ts | 15 +++---- .../add-device-profile-dialog.component.html | 4 -- .../add-device-profile-dialog.component.ts | 2 - .../profile/device-profile.component.html | 8 ++++ .../profile/device-profile.component.ts | 5 +++ .../device-wizard-dialog.component.html | 4 -- .../wizard/device-wizard-dialog.component.ts | 2 - .../home/pages/device/device.component.html | 8 ++++ .../home/pages/device/device.component.ts | 30 +++++++++----- .../firmware/firmware-table-config.resolve.ts | 18 ++++++--- .../pages/firmware/firmwares.component.html | 19 ++++++++- .../pages/firmware/firmwares.component.ts | 14 ++++++- .../firmware-autocomplete.component.html | 4 +- .../firmware-autocomplete.component.ts | 40 +++++++++++++++++-- ui-ngx/src/app/shared/models/device.models.ts | 2 + .../src/app/shared/models/firmware.models.ts | 19 ++++++++- .../assets/locale/locale.constant-en_US.json | 9 +++++ 18 files changed, 159 insertions(+), 46 deletions(-) diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 04d2db610d..c5ef4b8db4 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -361,7 +361,7 @@ export class EntityService { break; case EntityType.FIRMWARE: pageLink.sortOrder.property = 'title'; - entitiesObservable = this.firmwareService.getFirmwares(pageLink, true, config); + entitiesObservable = this.firmwareService.getFirmwares(pageLink, config); break; } return entitiesObservable; diff --git a/ui-ngx/src/app/core/http/firmware.service.ts b/ui-ngx/src/app/core/http/firmware.service.ts index 7bf42fb49e..8c3249aee6 100644 --- a/ui-ngx/src/app/core/http/firmware.service.ts +++ b/ui-ngx/src/app/core/http/firmware.service.ts @@ -20,7 +20,7 @@ import { PageLink } from '@shared/models/page/page-link'; import { defaultHttpOptionsFromConfig, defaultHttpUploadOptions, RequestConfig } from '@core/http/http-utils'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; -import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; +import { Firmware, FirmwareInfo, FirmwareType } from '@shared/models/firmware.models'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { deepClone, isDefinedAndNotNull } from '@core/utils'; @@ -34,12 +34,13 @@ export class FirmwareService { } - public getFirmwares(pageLink: PageLink, hasData?: boolean, config?: RequestConfig): Observable> { - let url = `/api/firmwares`; - if (isDefinedAndNotNull(hasData)) { - url += `/${hasData}`; - } - url += `${pageLink.toQuery()}`; + public getFirmwares(pageLink: PageLink, config?: RequestConfig): Observable> { + return this.http.get>(`/api/firmwares${pageLink.toQuery()}`, defaultHttpOptionsFromConfig(config)); + } + + public getFirmwaresInfoByDeviceProfileId(pageLink: PageLink, deviceProfileId: string, type: FirmwareType, + hasData = true, config?: RequestConfig): Observable> { + const url = `/api/firmwares/${deviceProfileId}/${type}/${hasData}${pageLink.toQuery()}`; return this.http.get>(url, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index a4f9fc3482..23219f1074 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -60,10 +60,6 @@ {{ 'device-profile.type-required' | translate }}
- - device-profile.description diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index 83f248db92..9ad7993857 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -108,7 +108,6 @@ export class AddDeviceProfileDialogComponent extends type: [DeviceProfileType.DEFAULT, [Validators.required]], defaultRuleChainId: [null, []], defaultQueueName: ['', []], - firmwareId: [null], description: ['', []] } ); @@ -187,7 +186,6 @@ export class AddDeviceProfileDialogComponent extends transportType: this.transportConfigFormGroup.get('transportType').value, provisionType: deviceProvisionConfiguration.type, provisionDeviceKey, - firmwareId: this.deviceProfileDetailsFormGroup.get('firmwareId').value, description: this.deviceProfileDetailsFormGroup.get('description').value, profileData: { configuration: createDeviceProfileConfiguration(DeviceProfileType.DEFAULT), diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 5f7593a03b..5c21d65adc 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -65,8 +65,16 @@ + + device-profile.type 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 23f1bd511c..d8505a0a3b 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 @@ -40,6 +40,7 @@ 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 { EntityId } from '@shared/models/id/entity-id'; +import { FirmwareType } from '@shared/models/firmware.models'; @Component({ selector: 'tb-device-profile', @@ -69,6 +70,8 @@ export class DeviceProfileComponent extends EntityComponent { deviceProfileId: EntityId; + firmwareTypes = FirmwareType; + constructor(protected store: Store, protected translate: TranslateService, @Optional() @Inject('entity') protected entityValue: DeviceProfile, @@ -110,6 +113,7 @@ export class DeviceProfileComponent extends EntityComponent { defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], defaultQueueName: [entity ? entity.defaultQueueName : '', []], firmwareId: [entity ? entity.firmwareId : null], + softwareId: [entity ? entity.softwareId : null], description: [entity ? entity.description : '', []], } ); @@ -186,6 +190,7 @@ export class DeviceProfileComponent extends EntityComponent { this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}, {emitEvent: false}); this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}, {emitEvent: false}); this.entityForm.patchValue({firmwareId: entity.firmwareId}, {emitEvent: false}); + this.entityForm.patchValue({softwareId: entity.softwareId}, {emitEvent: false}); this.entityForm.patchValue({description: entity.description}, {emitEvent: false}); } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 416b006499..245cb6503b 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -48,10 +48,6 @@ device.label - - device-profile.transport-type diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index df925e4e21..4e2b5b2ff8 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -107,7 +107,6 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup = this.fb.group({ name: ['', Validators.required], label: [''], - firmwareId: [null], gateway: [false], overwriteActivityTime: [false], transportType: [DeviceTransportType.DEFAULT, Validators.required], @@ -313,7 +312,6 @@ export class DeviceWizardDialogComponent extends const device = { name: this.deviceWizardFormGroup.get('name').value, label: this.deviceWizardFormGroup.get('label').value, - firmwareId: this.deviceWizardFormGroup.get('firmwareId').value, deviceProfileId: profileId, additionalInfo: { gateway: this.deviceWizardFormGroup.get('gateway').value, 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 be2ce1367d..f09437662a 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 @@ -103,8 +103,16 @@ + + 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 34335b5e2f..d7f21fb2fc 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 @@ -34,6 +34,7 @@ import { ActionNotificationShow } from '@core/notification/notification.actions' import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { Subject } from 'rxjs'; +import { FirmwareType } from '@shared/models/firmware.models'; @Component({ selector: 'tb-device', @@ -48,6 +49,8 @@ export class DeviceComponent extends EntityComponent { deviceScope: 'tenant' | 'customer' | 'customer_user' | 'edge'; + firmwareTypes = FirmwareType; + constructor(protected store: Store, protected translate: TranslateService, @Inject('entity') protected entityValue: DeviceInfo, @@ -80,6 +83,7 @@ export class DeviceComponent extends EntityComponent { name: [entity ? entity.name : '', [Validators.required]], deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]], firmwareId: [entity ? entity.firmwareId : null], + softwareId: [entity ? entity.softwareId : null], label: [entity ? entity.label : ''], deviceData: [entity ? entity.deviceData : null, [Validators.required]], additionalInfo: this.fb.group( @@ -94,19 +98,19 @@ export class DeviceComponent extends EntityComponent { } updateForm(entity: DeviceInfo) { - this.entityForm.patchValue({name: entity.name}); - this.entityForm.patchValue({deviceProfileId: entity.deviceProfileId}); - this.entityForm.patchValue({firmwareId: entity.firmwareId}); - this.entityForm.patchValue({label: entity.label}); - this.entityForm.patchValue({deviceData: entity.deviceData}); this.entityForm.patchValue({ - additionalInfo: - { - gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false, - overwriteActivityTime: entity.additionalInfo ? entity.additionalInfo.overwriteActivityTime : false - } + name: entity.name, + deviceProfileId: entity.deviceProfileId, + firmwareId: entity.firmwareId, + softwareId: entity.softwareId, + label: entity.label, + deviceData: entity.deviceData, + additionalInfo: { + gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false, + overwriteActivityTime: entity.additionalInfo ? entity.additionalInfo.overwriteActivityTime : false, + description: entity.additionalInfo ? entity.additionalInfo.description : '' + } }); - this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); } @@ -152,6 +156,10 @@ export class DeviceComponent extends EntityComponent { this.entityForm.markAsDirty(); } } + this.entityForm.patchValue({ + firmwareId: null, + softwareId: null + }); } } } diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts index b809f6f403..23efe2117b 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmware-table-config.resolve.ts @@ -21,7 +21,12 @@ import { EntityTableColumn, EntityTableConfig } from '@home/models/entity/entities-table-config.models'; -import { Firmware, FirmwareInfo } from '@shared/models/firmware.models'; +import { + ChecksumAlgorithmTranslationMap, + Firmware, + FirmwareInfo, + FirmwareTypeTranslationMap +} from '@shared/models/firmware.models'; import { EntityType, entityTypeResources, entityTypeTranslations } from '@shared/models/entity-type.models'; import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; @@ -49,14 +54,17 @@ export class FirmwareTableConfigResolve implements Resolve('createdTime', 'common.created-time', this.datePipe, '150px'), - new EntityTableColumn('title', 'firmware.title', '33%'), - new EntityTableColumn('version', 'firmware.version', '33%'), - new EntityTableColumn('fileName', 'firmware.file-name', '33%'), + new EntityTableColumn('title', 'firmware.title', '25%'), + new EntityTableColumn('version', 'firmware.version', '25%'), + new EntityTableColumn('type', 'firmware.type', '25%', entity => { + return this.translate.instant(FirmwareTypeTranslationMap.get(entity.type)); + }), + new EntityTableColumn('fileName', 'firmware.file-name', '25%'), new EntityTableColumn('dataSize', 'firmware.file-size', '70px', entity => { return this.fileSize.transform(entity.dataSize || 0); }), new EntityTableColumn('checksum', 'firmware.checksum', '540px', entity => { - return `${entity.checksumAlgorithm}: ${entity.checksum}`; + return `${ChecksumAlgorithmTranslationMap.get(entity.checksumAlgorithm)}: ${entity.checksum}`; }, () => ({}), false) ); diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html index 2911802a75..cba515f1d8 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.html @@ -67,10 +67,27 @@
+
+ + firmware.type + + + + {{ firmwareTypeTranslationMap.get(firmwareType) | translate }} + + + + + +
firmware.checksum-algorithm - + diff --git a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts index 8ffef239b4..adfd2050c7 100644 --- a/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts +++ b/ui-ngx/src/app/modules/home/pages/firmware/firmwares.component.ts @@ -22,7 +22,13 @@ import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { EntityComponent } from '@home/components/entity/entity.component'; -import { ChecksumAlgorithm, ChecksumAlgorithmTranslationMap, Firmware } from '@shared/models/firmware.models'; +import { + ChecksumAlgorithm, + ChecksumAlgorithmTranslationMap, + Firmware, + FirmwareType, + FirmwareTypeTranslationMap +} from '@shared/models/firmware.models'; import { distinctUntilChanged, map, takeUntil } from 'rxjs/operators'; import { ActionNotificationShow } from '@core/notification/notification.actions'; @@ -36,6 +42,8 @@ export class FirmwaresComponent extends EntityComponent implements OnI checksumAlgorithms = Object.values(ChecksumAlgorithm); checksumAlgorithmTranslationMap = ChecksumAlgorithmTranslationMap; + firmwareTypes = Object.values(FirmwareType); + firmwareTypeTranslationMap = FirmwareTypeTranslationMap; constructor(protected store: Store, protected translate: TranslateService, @@ -83,6 +91,8 @@ export class FirmwaresComponent extends EntityComponent implements OnI const form = this.fb.group({ title: [entity ? entity.title : '', [Validators.required, Validators.maxLength(255)]], version: [entity ? entity.version : '', [Validators.required, Validators.maxLength(255)]], + type: [entity?.type ? entity.type : FirmwareType.FIRMWARE, [Validators.required]], + deviceProfileId: [entity ? entity.deviceProfileId : null], checksumAlgorithm: [entity ? entity.checksumAlgorithm : null], checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)], additionalInfo: this.fb.group( @@ -105,6 +115,8 @@ export class FirmwaresComponent extends EntityComponent implements OnI this.entityForm.patchValue({ title: entity.title, version: entity.version, + type: entity.type, + deviceProfileId: entity.deviceProfileId, checksumAlgorithm: entity.checksumAlgorithm, checksum: entity.checksum, fileName: entity.fileName, diff --git a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html b/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html index 4e733b3132..7bb434e7d1 100644 --- a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.html @@ -37,11 +37,11 @@
- firmware.no-firmware-text + {{ notFoundFirmware | translate }}
- {{ translate.get('firmware.no-firmware-matching', + {{ translate.get(notMatchingFirmware, {entity: truncate.transform(searchText, true, 6, '...')}) | async }} diff --git a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts b/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts index 79dc170c7b..ccdf16aedd 100644 --- a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts @@ -28,7 +28,7 @@ import { BaseData } from '@shared/models/base-data'; import { EntityService } from '@core/http/entity.service'; import { TruncatePipe } from '@shared/pipe/truncate.pipe'; import { MatAutocompleteTrigger } from '@angular/material/autocomplete'; -import { FirmwareInfo } from '@shared/models/firmware.models'; +import { FirmwareInfo, FirmwareType } from '@shared/models/firmware.models'; import { FirmwareService } from '@core/http/firmware.service'; import { PageLink } from '@shared/models/page/page-link'; import { Direction } from '@shared/models/page/sort-order'; @@ -49,6 +49,12 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn modelValue: string | null; + @Input() + type = FirmwareType.FIRMWARE; + + @Input() + deviceProfileId: string; + @Input() labelText: string; @@ -81,6 +87,23 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn private dirty = false; + private firmwareTypeTranslation = new Map( + [ + [FirmwareType.FIRMWARE, { + label: 'firmware.firmware', + required: 'firmware.firmware-required', + noFound: 'firmware.no-firmware-text', + noMatching: 'firmware.no-firmware-matching' + }], + [FirmwareType.SOFTWARE, { + label: 'firmware.software', + required: 'firmware.software-required', + noFound: 'firmware.no-software-text', + noMatching: 'firmware.no-software-matching' + }] + ] + ); + private propagateChange = (v: any) => { }; constructor(private store: Store, @@ -209,7 +232,8 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn property: 'title', direction: Direction.ASC }); - return this.firmwareService.getFirmwares(pageLink, true, {ignoreLoading: true}).pipe( + return this.firmwareService.getFirmwaresInfoByDeviceProfileId(pageLink, this.deviceProfileId, this.type, + true, {ignoreLoading: true}).pipe( map((data) => data && data.data.length ? data.data : null) ); } @@ -223,11 +247,19 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn } get placeholderText(): string { - return this.labelText || 'firmware.firmware'; + return this.labelText || this.firmwareTypeTranslation.get(this.type).label; } get requiredErrorText(): string { - return this.requiredText || 'firmware.firmware-required'; + return this.requiredText || this.firmwareTypeTranslation.get(this.type).required; + } + + get notFoundFirmware(): string { + return this.firmwareTypeTranslation.get(this.type).noFound; + } + + get notMatchingFirmware(): string { + return this.firmwareTypeTranslation.get(this.type).noMatching; } firmwareTitleText(firmware: FirmwareInfo): string { diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 6067eeee38..42c1689006 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -467,6 +467,7 @@ export interface DeviceProfile extends BaseData { defaultRuleChainId?: RuleChainId; defaultQueueName?: string; firmwareId?: FirmwareId; + softwareId?: FirmwareId; profileData: DeviceProfileData; } @@ -522,6 +523,7 @@ export interface Device extends BaseData { type: string; label: string; firmwareId?: FirmwareId; + softwareId?: FirmwareId; deviceProfileId?: DeviceProfileId; deviceData?: DeviceData; additionalInfo?: any; diff --git a/ui-ngx/src/app/shared/models/firmware.models.ts b/ui-ngx/src/app/shared/models/firmware.models.ts index 65dd1761bf..57c95c3c9a 100644 --- a/ui-ngx/src/app/shared/models/firmware.models.ts +++ b/ui-ngx/src/app/shared/models/firmware.models.ts @@ -17,6 +17,7 @@ import { BaseData } from '@shared/models/base-data'; import { TenantId } from '@shared/models/id/tenant-id'; import { FirmwareId } from '@shared/models/id/firmware-id'; +import { DeviceProfileId } from '@shared/models/id/device-profile-id'; export enum ChecksumAlgorithm { MD5 = 'md5', @@ -32,14 +33,28 @@ export const ChecksumAlgorithmTranslationMap = new Map( + [ + [FirmwareType.FIRMWARE, 'firmware.types.firmware'], + [FirmwareType.SOFTWARE, 'firmware.types.software'] + ] +); + export interface FirmwareInfo extends BaseData { tenantId?: TenantId; + type: FirmwareType; + deviceProfileId?: DeviceProfileId; title?: string; version?: string; hasData?: boolean; fileName: string; - checksum?: ChecksumAlgorithm; - checksumAlgorithm?: string; + checksum?: string; + checksumAlgorithm?: ChecksumAlgorithm; contentType: string; dataSize?: number; additionalInfo?: 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 d59ee56236..5b90961b29 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1928,6 +1928,8 @@ "idCopiedMessage": "Firmware Id has been copied to clipboard", "no-firmware-matching": "No firmware matching '{{entity}}' were found.", "no-firmware-text": "No firmwares found", + "no-software-matching": "No sowtware matching '{{entity}}' were found.", + "no-software-text": "No software found", "file-name": "File name", "file-size": "File size", "file-size-bytes": "File size in bytes", @@ -1936,8 +1938,15 @@ "firmware-required": "Firmware is required.", "search": "Search firmwares", "selected-firmware": "{ count, plural, 1 {1 firmware} other {# firmwares} } selected", + "software": "Software", + "software-required": "Software is required.", "title": "Title", "title-required": "Title is required.", + "type": "Firmware type", + "types": { + "firmware": "Firmware", + "software": "Software" + }, "version": "Version", "version-required": "Version is required.", "warning-after-save-no-edit": "Once the firmware is saved, it will not be possible to change the title and version fields." From 4d4ca9b723f295b798448ddde1d40d56dd3f5a81 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 29 Apr 2021 17:00:05 +0300 Subject: [PATCH 113/127] firmware tests and validation improvements --- .../main/data/upgrade/3.2.2/schema_update.sql | 19 ++++- .../dao/device/DeviceProfileServiceImpl.java | 13 +++ .../server/dao/device/DeviceServiceImpl.java | 7 ++ .../dao/firmware/BaseFirmwareService.java | 16 ++-- .../server/dao/firmware/FirmwareInfoDao.java | 3 + .../server/dao/model/sql/FirmwareEntity.java | 10 ++- .../dao/model/sql/FirmwareInfoEntity.java | 8 +- .../sql/firmware/FirmwareInfoRepository.java | 9 ++ .../dao/sql/firmware/JpaFirmwareInfoDao.java | 6 ++ .../resources/sql/schema-entities-hsql.sql | 2 +- .../main/resources/sql/schema-entities.sql | 2 +- .../dao/service/BaseFirmwareServiceTest.java | 83 +++++++++++++++---- 12 files changed, 145 insertions(+), 33 deletions(-) diff --git a/application/src/main/data/upgrade/3.2.2/schema_update.sql b/application/src/main/data/upgrade/3.2.2/schema_update.sql index da120f680e..af3fd418a8 100644 --- a/application/src/main/data/upgrade/3.2.2/schema_update.sql +++ b/application/src/main/data/upgrade/3.2.2/schema_update.sql @@ -63,6 +63,7 @@ CREATE TABLE IF NOT EXISTS firmware ( id uuid NOT NULL CONSTRAINT firmware_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid NOT NULL, + device_profile_id uuid, type varchar(32) NOT NULL, title varchar(255) NOT NULL, version varchar(255) NOT NULL, @@ -78,10 +79,12 @@ CREATE TABLE IF NOT EXISTS firmware ( ); ALTER TABLE device_profile - ADD COLUMN IF NOT EXISTS firmware_id uuid; + ADD COLUMN IF NOT EXISTS firmware_id uuid, + ADD COLUMN IF NOT EXISTS software_id uuid; ALTER TABLE device - ADD COLUMN IF NOT EXISTS firmware_id uuid; + ADD COLUMN IF NOT EXISTS firmware_id uuid, + ADD COLUMN IF NOT EXISTS software_id uuid; DO $$ BEGIN @@ -91,11 +94,23 @@ DO $$ FOREIGN KEY (firmware_id) REFERENCES firmware(id); END IF; + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device_profile') THEN + ALTER TABLE device_profile + ADD CONSTRAINT fk_software_device_profile + FOREIGN KEY (firmware_id) REFERENCES firmware(id); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device') THEN ALTER TABLE device ADD CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES firmware(id); END IF; + + IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_software_device') THEN + ALTER TABLE device + ADD CONSTRAINT fk_software_device + FOREIGN KEY (firmware_id) REFERENCES firmware(id); + END IF; END; $$; 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 020fd7c524..9df20674c8 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 @@ -56,6 +56,7 @@ import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfilePr 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.firmware.FirmwareType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -405,9 +406,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (firmware == null) { throw new DataValidationException("Can't assign non-existent firmware!"); } + if (!firmware.getType().equals(FirmwareType.FIRMWARE)) { + throw new DataValidationException("Can't assign firmware with type: " + firmware.getType()); + } if (firmware.getData() == null) { throw new DataValidationException("Can't assign firmware with empty data!"); } + if (!firmware.getDeviceProfileId().equals(deviceProfile.getId())) { + throw new DataValidationException("Can't assign firmware with different deviceProfile!"); + } } if (deviceProfile.getSoftwareId() != null) { @@ -415,9 +422,15 @@ public class DeviceProfileServiceImpl extends AbstractEntityService implements D if (software == null) { throw new DataValidationException("Can't assign non-existent software!"); } + if (!software.getType().equals(FirmwareType.SOFTWARE)) { + throw new DataValidationException("Can't assign software with type: " + software.getType()); + } if (software.getData() == null) { throw new DataValidationException("Can't assign software with empty data!"); } + if (!software.getDeviceProfileId().equals(deviceProfile.getId())) { + throw new DataValidationException("Can't assign firmware with different deviceProfile!"); + } } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index dd9b46b62f..11d2fecd24 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -51,6 +51,7 @@ import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.Lwm2mDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -683,6 +684,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe if (firmware == null) { throw new DataValidationException("Can't assign non-existent firmware!"); } + if (!firmware.getType().equals(FirmwareType.FIRMWARE)) { + throw new DataValidationException("Can't assign firmware with type: " + firmware.getType()); + } if (firmware.getData() == null) { throw new DataValidationException("Can't assign firmware with empty data!"); } @@ -696,6 +700,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe if (software == null) { throw new DataValidationException("Can't assign non-existent software!"); } + if (!software.getType().equals(FirmwareType.SOFTWARE)) { + throw new DataValidationException("Can't assign software with type: " + software.getType()); + } if (software.getData() == null) { throw new DataValidationException("Can't assign software with empty data!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java index 207d629bbe..6ac3df8292 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/BaseFirmwareService.java @@ -191,6 +191,7 @@ public class BaseFirmwareService implements FirmwareService { protected void validateUpdate(TenantId tenantId, FirmwareInfo firmware) { FirmwareInfo firmwareOld = firmwareInfoDao.findById(tenantId, firmware.getUuidId()); + validateUpdateDeviceProfile(firmware, firmwareOld); BaseFirmwareService.validateUpdate(firmware, firmwareOld); } }; @@ -247,6 +248,7 @@ public class BaseFirmwareService implements FirmwareService { protected void validateUpdate(TenantId tenantId, Firmware firmware) { Firmware firmwareOld = firmwareDao.findById(tenantId, firmware.getUuidId()); + validateUpdateDeviceProfile(firmware, firmwareOld); BaseFirmwareService.validateUpdate(firmware, firmwareOld); if (firmwareOld.getData() != null && !firmwareOld.getData().equals(firmware.getData())) { @@ -255,11 +257,15 @@ public class BaseFirmwareService implements FirmwareService { } }; - private static void validateUpdate(FirmwareInfo firmware, FirmwareInfo firmwareOld) { - if (!firmwareOld.getDeviceProfileId().equals(firmware.getDeviceProfileId())) { - throw new DataValidationException("Updating firmware deviceProfile is prohibited!"); + private void validateUpdateDeviceProfile(FirmwareInfo firmware, FirmwareInfo firmwareOld) { + if (firmwareOld.getDeviceProfileId() != null && !firmwareOld.getDeviceProfileId().equals(firmware.getDeviceProfileId())) { + if (firmwareInfoDao.isFirmwareUsed(firmwareOld.getId(), firmware.getType(), firmwareOld.getDeviceProfileId())) { + throw new DataValidationException("Can`t update deviceProfileId because firmware is already in use!"); + } } + } + private static void validateUpdate(FirmwareInfo firmware, FirmwareInfo firmwareOld) { if (!firmwareOld.getType().equals(firmware.getType())) { throw new DataValidationException("Updating type is prohibited!"); } @@ -303,9 +309,7 @@ public class BaseFirmwareService implements FirmwareService { } } - if (firmwareInfo.getDeviceProfileId() == null) { - throw new DataValidationException("Firmware should be assigned to deviceProfile!"); - } else { + if (firmwareInfo.getDeviceProfileId() != null) { DeviceProfile deviceProfile = deviceProfileDao.findById(firmwareInfo.getTenantId(), firmwareInfo.getDeviceProfileId().getId()); if (deviceProfile == null) { throw new DataValidationException("Firmware is referencing to non-existent device profile!"); diff --git a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java index 32ca8dc528..7cb6c3a57f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/firmware/FirmwareInfoDao.java @@ -18,6 +18,7 @@ package org.thingsboard.server.dao.firmware; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -31,4 +32,6 @@ public interface FirmwareInfoDao extends Dao { PageData findFirmwareInfoByTenantIdAndDeviceProfileIdAndTypeAndHasData(TenantId tenantId, DeviceProfileId deviceProfileId, FirmwareType firmwareType, boolean hasData, PageLink pageLink); + boolean isFirmwareUsed(FirmwareId firmwareId, FirmwareType type, DeviceProfileId deviceProfileId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java index a10ec8f7ed..4f3aded716 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareEntity.java @@ -32,9 +32,9 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.Lob; import javax.persistence.EnumType; import javax.persistence.Enumerated; +import javax.persistence.Lob; import javax.persistence.Table; import java.nio.ByteBuffer; import java.util.UUID; @@ -110,7 +110,9 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex this.createdTime = firmware.getCreatedTime(); this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); - this.deviceProfileId = firmware.getDeviceProfileId().getId(); + if (firmware.getDeviceProfileId() != null) { + this.deviceProfileId = firmware.getDeviceProfileId().getId(); + } this.type = firmware.getType(); this.title = firmware.getTitle(); this.version = firmware.getVersion(); @@ -138,7 +140,9 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex Firmware firmware = new Firmware(new FirmwareId(id)); firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); - firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); + if (deviceProfileId != null) { + firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); + } firmware.setType(type); firmware.setTitle(title); firmware.setVersion(version); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java index b1e058a012..bb62db5ea4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/FirmwareInfoEntity.java @@ -109,7 +109,9 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S this.setUuid(firmware.getUuidId()); this.tenantId = firmware.getTenantId().getId(); this.type = firmware.getType(); - this.deviceProfileId = firmware.getDeviceProfileId().getId(); + if (firmware.getDeviceProfileId() != null) { + this.deviceProfileId = firmware.getDeviceProfileId().getId(); + } this.title = firmware.getTitle(); this.version = firmware.getVersion(); this.fileName = firmware.getFileName(); @@ -154,7 +156,9 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S FirmwareInfo firmware = new FirmwareInfo(new FirmwareId(id)); firmware.setCreatedTime(createdTime); firmware.setTenantId(new TenantId(tenantId)); - firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); + if (deviceProfileId != null) { + firmware.setDeviceProfileId(new DeviceProfileId(deviceProfileId)); + } firmware.setType(type); firmware.setTitle(title); firmware.setVersion(version); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java index 5a7af9997b..dab6ce3304 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/firmware/FirmwareInfoRepository.java @@ -48,4 +48,13 @@ public interface FirmwareInfoRepository extends CrudRepository Date: Fri, 30 Apr 2021 10:52:07 +0300 Subject: [PATCH 114/127] implemented software update --- .../controller/DeviceProfileController.java | 9 +- .../firmware/DefaultFirmwareStateService.java | 108 +++++++++++++++--- .../firmware/FirmwareStateService.java | 2 +- .../transport/DefaultTransportApiService.java | 22 +++- .../server/dao/device/DeviceService.java | 2 + .../data/device/profile/MqttTopics.java | 13 ++- .../common/data/firmware/FirmwareKeyUtil.java | 34 +++++- .../common/msg/session/FeatureType.java | 2 +- .../common/msg/session/SessionMsgType.java | 3 +- common/queue/src/main/proto/queue.proto | 2 + .../transport/coap/CoapTransportResource.java | 23 +++- .../transport/http/DeviceApiController.java | 37 ++++-- .../transport/mqtt/MqttTransportHandler.java | 88 ++++++++------ .../mqtt/adaptors/JsonMqttAdaptor.java | 8 +- .../mqtt/adaptors/MqttTransportAdaptor.java | 3 +- .../mqtt/adaptors/ProtoMqttAdaptor.java | 7 +- .../server/dao/device/DeviceDao.java | 2 + .../server/dao/device/DeviceServiceImpl.java | 11 +- .../dao/model/sql/AbstractDeviceEntity.java | 2 +- .../dao/sql/device/DeviceRepository.java | 9 ++ .../server/dao/sql/device/JpaDeviceDao.java | 10 ++ .../firmware-autocomplete.component.ts | 3 +- 22 files changed, 308 insertions(+), 92 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 225c79b79a..849bcb51b9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java @@ -146,12 +146,16 @@ public class DeviceProfileController extends BaseController { checkEntity(deviceProfile.getId(), deviceProfile, Resource.DEVICE_PROFILE); boolean isFirmwareChanged = false; + boolean isSoftwareChanged = false; if (!created) { DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(getTenantId(), deviceProfile.getId()); if (!Objects.equals(deviceProfile.getFirmwareId(), oldDeviceProfile.getFirmwareId())) { isFirmwareChanged = true; } + if (!Objects.equals(deviceProfile.getSoftwareId(), oldDeviceProfile.getSoftwareId())) { + isSoftwareChanged = true; + } } DeviceProfile savedDeviceProfile = checkNotNull(deviceProfileService.saveDeviceProfile(deviceProfile)); @@ -164,9 +168,8 @@ public class DeviceProfileController extends BaseController { null, created ? ActionType.ADDED : ActionType.UPDATED, null); - if (isFirmwareChanged) { - firmwareStateService.update(savedDeviceProfile); - } + firmwareStateService.update(savedDeviceProfile, isFirmwareChanged, isSoftwareChanged); + sendEntityNotificationMsg(getTenantId(), savedDeviceProfile.getId(), deviceProfile.getId() == null ? EdgeEventActionType.ADDED : EdgeEventActionType.UPDATED); return savedDeviceProfile; diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index c4ad9feceb..a09ac3bb31 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.FirmwareInfo; import org.thingsboard.server.common.data.firmware.FirmwareKeyUtil; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; @@ -56,6 +57,7 @@ import java.util.List; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; +import java.util.function.Function; import static org.thingsboard.server.common.data.firmware.FirmwareKey.CHECKSUM; import static org.thingsboard.server.common.data.firmware.FirmwareKey.CHECKSUM_ALGORITHM; @@ -67,6 +69,8 @@ import static org.thingsboard.server.common.data.firmware.FirmwareKey.VERSION; import static org.thingsboard.server.common.data.firmware.FirmwareKeyUtil.getAttributeKey; import static org.thingsboard.server.common.data.firmware.FirmwareKeyUtil.getTargetTelemetryKey; import static org.thingsboard.server.common.data.firmware.FirmwareKeyUtil.getTelemetryKey; +import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; +import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE; @Slf4j @Service @@ -95,6 +99,11 @@ public class DefaultFirmwareStateService implements FirmwareStateService { @Override public void update(Device device, Device oldDevice) { + updateFirmware(device, oldDevice); + updateSoftware(device, oldDevice); + } + + private void updateFirmware(Device device, Device oldDevice) { FirmwareId newFirmwareId = device.getFirmwareId(); if (newFirmwareId == null) { DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); @@ -109,35 +118,84 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } if (!newFirmwareId.equals(oldFirmwareId)) { // Device was updated and new firmware is different from previous firmware. - send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis()); + send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE); } } else { // Device was updated and new firmware is not set. - remove(device); + remove(device, FIRMWARE); } } else if (newFirmwareId != null) { // Device was created and firmware is defined. - send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis()); + send(device.getTenantId(), device.getId(), newFirmwareId, System.currentTimeMillis(), FIRMWARE); + } + } + + private void updateSoftware(Device device, Device oldDevice) { + FirmwareId newSoftwareId = device.getSoftwareId(); + if (newSoftwareId == null) { + DeviceProfile newDeviceProfile = deviceProfileService.findDeviceProfileById(device.getTenantId(), device.getDeviceProfileId()); + newSoftwareId = newDeviceProfile.getSoftwareId(); + } + if (oldDevice != null) { + if (newSoftwareId != null) { + FirmwareId oldSoftwareId = oldDevice.getSoftwareId(); + if (oldSoftwareId == null) { + DeviceProfile oldDeviceProfile = deviceProfileService.findDeviceProfileById(oldDevice.getTenantId(), oldDevice.getDeviceProfileId()); + oldSoftwareId = oldDeviceProfile.getSoftwareId(); + } + if (!newSoftwareId.equals(oldSoftwareId)) { + // Device was updated and new firmware is different from previous firmware. + send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE); + } + } else { + // Device was updated and new firmware is not set. + remove(device, SOFTWARE); + } + } else if (newSoftwareId != null) { + // Device was created and firmware is defined. + send(device.getTenantId(), device.getId(), newSoftwareId, System.currentTimeMillis(), SOFTWARE); } } @Override - public void update(DeviceProfile deviceProfile) { + public void update(DeviceProfile deviceProfile, boolean isFirmwareChanged, boolean isSoftwareChanged) { TenantId tenantId = deviceProfile.getTenantId(); + if (isFirmwareChanged) { + update(tenantId, deviceProfile, FIRMWARE); + } + if (isSoftwareChanged) { + update(tenantId, deviceProfile, SOFTWARE); + } + } + + private void update(TenantId tenantId, DeviceProfile deviceProfile, FirmwareType firmwareType) { + Function> getDevicesFunction; Consumer updateConsumer; + + switch (firmwareType) { + case FIRMWARE: + getDevicesFunction = pl -> deviceService.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId, deviceProfile.getName(), pl); + break; + case SOFTWARE: + getDevicesFunction = pl -> deviceService.findDevicesByTenantIdAndTypeAndEmptySoftware(tenantId, deviceProfile.getName(), pl); + break; + default: + log.warn("Unsupported firmware type: [{}]", firmwareType); + return; + } + if (deviceProfile.getFirmwareId() != null) { long ts = System.currentTimeMillis(); - updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts); + updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, firmwareType); } else { - updateConsumer = this::remove; + updateConsumer = d -> remove(d, firmwareType); } PageLink pageLink = new PageLink(100); PageData pageData; do { - pageData = deviceService.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId, deviceProfile.getName(), pageLink); - + pageData = getDevicesFunction.apply(pageLink); pageData.getData().forEach(updateConsumer); if (pageData.hasNext()) { @@ -152,16 +210,37 @@ public class DefaultFirmwareStateService implements FirmwareStateService { FirmwareId targetFirmwareId = new FirmwareId(new UUID(msg.getFirmwareIdMSB(), msg.getFirmwareIdLSB())); DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); TenantId tenantId = new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); + FirmwareType firmwareType = FirmwareType.valueOf(msg.getType()); long ts = msg.getTs(); Device device = deviceService.findDeviceById(tenantId, deviceId); if (device == null) { log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId); } else { - FirmwareId currentFirmwareId = device.getFirmwareId(); + FirmwareId currentFirmwareId; + + switch (firmwareType) { + case FIRMWARE: + currentFirmwareId = device.getFirmwareId(); + break; + case SOFTWARE: + currentFirmwareId = device.getSoftwareId(); + break; + default: + log.warn("Unsupported firmware type: [{}]", firmwareType); + return false; + } if (currentFirmwareId == null) { - currentFirmwareId = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId()).getFirmwareId(); + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId()); + switch (firmwareType) { + case FIRMWARE: + currentFirmwareId = deviceProfile.getFirmwareId(); + break; + case SOFTWARE: + currentFirmwareId = deviceProfile.getSoftwareId(); + break; + } } if (targetFirmwareId.equals(currentFirmwareId)) { @@ -174,7 +253,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { return isSuccess; } - private void send(TenantId tenantId, DeviceId deviceId, FirmwareId firmwareId, long ts) { + private void send(TenantId tenantId, DeviceId deviceId, FirmwareId firmwareId, long ts, FirmwareType firmwareType) { ToFirmwareStateServiceMsg msg = ToFirmwareStateServiceMsg.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) @@ -182,6 +261,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) .setFirmwareIdMSB(firmwareId.getId().getMostSignificantBits()) .setFirmwareIdLSB(firmwareId.getId().getLeastSignificantBits()) + .setType(firmwareType.name()) .setTs(ts) .build(); @@ -252,14 +332,14 @@ public class DefaultFirmwareStateService implements FirmwareStateService { }); } - private void remove(Device device) { - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, FirmwareKeyUtil.ALL_ATTRIBUTE_KEYS, + private void remove(Device device, FirmwareType firmwareType) { + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, FirmwareKeyUtil.getAttributeKeys(firmwareType), new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { log.trace("[{}] Success remove target firmware attributes!", device.getId()); Set keysToNotify = new HashSet<>(); - FirmwareKeyUtil.ALL_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); + FirmwareKeyUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null); } diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java index ac51b0d95e..8562f096c9 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/FirmwareStateService.java @@ -23,7 +23,7 @@ public interface FirmwareStateService { void update(Device device, Device oldDevice); - void update(DeviceProfile deviceProfile); + void update(DeviceProfile deviceProfile, boolean isFirmwareChanged, boolean isSoftwareChanged); boolean process(ToFirmwareStateServiceMsg msg); 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 463af51862..6a765dff63 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 @@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.device.credentials.BasicMqttCredentials; import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -455,16 +456,33 @@ public class DefaultTransportApiService implements TransportApiService { private ListenableFuture handle(TransportProtos.GetFirmwareRequestMsg requestMsg) { TenantId tenantId = new TenantId(new UUID(requestMsg.getTenantIdMSB(), requestMsg.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(requestMsg.getDeviceIdMSB(), requestMsg.getDeviceIdLSB())); + FirmwareType firmwareType = FirmwareType.valueOf(requestMsg.getType()); Device device = deviceService.findDeviceById(tenantId, deviceId); if (device == null) { return getEmptyTransportApiResponseFuture(); } - FirmwareId firmwareId = device.getFirmwareId(); + FirmwareId firmwareId = null; + switch (firmwareType) { + case FIRMWARE: + firmwareId = device.getFirmwareId(); + break; + case SOFTWARE: + firmwareId = device.getSoftwareId(); + break; + } if (firmwareId == null) { - firmwareId = deviceProfileCache.find(device.getDeviceProfileId()).getFirmwareId(); + DeviceProfile deviceProfile = deviceProfileCache.find(device.getDeviceProfileId()); + switch (firmwareType) { + case FIRMWARE: + firmwareId = deviceProfile.getFirmwareId(); + break; + case SOFTWARE: + firmwareId = deviceProfile.getSoftwareId(); + break; + } } TransportProtos.GetFirmwareResponseMsg.Builder builder = TransportProtos.GetFirmwareResponseMsg.newBuilder(); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java index afea29c394..26ea48ca2f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/device/DeviceService.java @@ -63,6 +63,8 @@ public interface DeviceService { PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(TenantId tenantId, String type, PageLink pageLink); + PageData findDevicesByTenantIdAndTypeAndEmptySoftware(TenantId tenantId, String type, PageLink pageLink); + PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); PageData findDeviceInfosByTenantIdAndDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId, PageLink pageLink); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java index 419f9c7f43..6373d41803 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/MqttTopics.java @@ -31,6 +31,7 @@ public class MqttTopics { private static final String SUB_TOPIC = "+"; private static final String PROVISION = "/provision"; private static final String FIRMWARE = "/fw"; + private static final String SOFTWARE = "/sw"; private static final String CHUNK = "/chunk/"; private static final String ERROR = "/error"; @@ -75,9 +76,17 @@ public class MqttTopics { // v2 topics public static final String BASE_DEVICE_API_TOPIC_V2 = "v2"; - public static final String DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/"; - public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + SUB_TOPIC + CHUNK + SUB_TOPIC; + public static final String REQUEST_ID_PATTERN = "(?\\d+)"; + public static final String CHUNK_PATTERN = "(?\\d+)"; + + public static final String DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN; + public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC; public static final String DEVICE_FIRMWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + FIRMWARE + ERROR; + public static final String DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT = BASE_DEVICE_API_TOPIC_V2 + "%s" + RESPONSE + "/"+ "%s" + CHUNK + "%d"; + + public static final String DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + REQUEST + "/" + REQUEST_ID_PATTERN + CHUNK + CHUNK_PATTERN; + public static final String DEVICE_SOFTWARE_RESPONSES_TOPIC = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + RESPONSE + "/" + SUB_TOPIC + CHUNK + SUB_TOPIC; + public static final String DEVICE_SOFTWARE_ERROR_TOPIC = BASE_DEVICE_API_TOPIC_V2 + SOFTWARE + ERROR; private MqttTopics() { } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKeyUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKeyUtil.java index 8863016978..4712d48430 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKeyUtil.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKeyUtil.java @@ -16,18 +16,40 @@ package org.thingsboard.server.common.data.firmware; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; +import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE; + public class FirmwareKeyUtil { - public static final List ALL_ATTRIBUTE_KEYS; + public static final List ALL_FW_ATTRIBUTE_KEYS; + + public static final List ALL_SW_ATTRIBUTE_KEYS; + static { - ALL_ATTRIBUTE_KEYS = new ArrayList<>(); - for (FirmwareType type : FirmwareType.values()) { - for (FirmwareKey key : FirmwareKey.values()) { - ALL_ATTRIBUTE_KEYS.add(getAttributeKey(type, key)); - } + ALL_FW_ATTRIBUTE_KEYS = new ArrayList<>(); + for (FirmwareKey key : FirmwareKey.values()) { + ALL_FW_ATTRIBUTE_KEYS.add(getAttributeKey(FIRMWARE, key)); + + } + + ALL_SW_ATTRIBUTE_KEYS = new ArrayList<>(); + for (FirmwareKey key : FirmwareKey.values()) { + ALL_SW_ATTRIBUTE_KEYS.add(getAttributeKey(SOFTWARE, key)); + + } + } + + public static List getAttributeKeys(FirmwareType firmwareType) { + switch (firmwareType) { + case FIRMWARE: + return ALL_FW_ATTRIBUTE_KEYS; + case SOFTWARE: + return ALL_SW_ATTRIBUTE_KEYS; } + return Collections.emptyList(); } public static String getAttributeKey(FirmwareType type, FirmwareKey key) { diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java index 2756276dc4..ae965e327a 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/FeatureType.java @@ -16,5 +16,5 @@ package org.thingsboard.server.common.msg.session; public enum FeatureType { - ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION, FIRMWARE + ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION, FIRMWARE, SOFTWARE } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java index a6f05018f8..939197af80 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/session/SessionMsgType.java @@ -32,7 +32,8 @@ public enum SessionMsgType { CLAIM_REQUEST(), - GET_FIRMWARE_REQUEST; + GET_FIRMWARE_REQUEST, + GET_SOFTWARE_REQUEST; private final boolean requiresRulesProcessing; diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index c1857e8933..25afefa12e 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -357,6 +357,7 @@ message GetFirmwareRequestMsg { int64 deviceIdLSB = 2; int64 tenantIdMSB = 3; int64 tenantIdLSB = 4; + string type = 5; } message GetFirmwareResponseMsg { @@ -675,4 +676,5 @@ message ToFirmwareStateServiceMsg { int64 deviceIdLSB = 5; int64 firmwareIdMSB = 6; int64 firmwareIdLSB = 7; + string type = 8; } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java index 468bc244c6..e287a28aa0 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java @@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.msg.session.SessionMsgType; @@ -122,6 +123,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { processRequest(exchange, SessionMsgType.GET_ATTRIBUTES_REQUEST); } else if (featureType.get() == FeatureType.FIRMWARE) { processRequest(exchange, SessionMsgType.GET_FIRMWARE_REQUEST); + } else if (featureType.get() == FeatureType.SOFTWARE) { + processRequest(exchange, SessionMsgType.GET_SOFTWARE_REQUEST); } else { log.trace("Invalid feature type parameter"); exchange.respond(CoAP.ResponseCode.BAD_REQUEST); @@ -326,12 +329,10 @@ public class CoapTransportResource extends AbstractCoapTransportResource { new CoapNoOpCallback(exchange)); break; case GET_FIRMWARE_REQUEST: - TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() - .setTenantIdMSB(sessionInfo.getTenantIdMSB()) - .setTenantIdLSB(sessionInfo.getTenantIdLSB()) - .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) - .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build(); - transportContext.getTransportService().process(sessionInfo, requestMsg, new FirmwareCallback(exchange)); + getFirmwareCallback(sessionInfo, exchange, FirmwareType.FIRMWARE); + break; + case GET_SOFTWARE_REQUEST: + getFirmwareCallback(sessionInfo, exchange, FirmwareType.SOFTWARE); break; } } catch (AdaptorException e) { @@ -340,6 +341,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } + private void getFirmwareCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, FirmwareType firmwareType) { + TransportProtos.GetFirmwareRequestMsg requestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) + .setType(firmwareType.name()).build(); + transportContext.getTransportService().process(sessionInfo, requestMsg, new FirmwareCallback(exchange)); + } + private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { tokenToNotificationCounterMap.remove(token); return tokenToSessionIdMap.remove(token); diff --git a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java index 62b46a5fe7..dcd5755d04 100644 --- a/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java +++ b/common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java @@ -34,6 +34,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.async.DeferredResult; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportContext; @@ -209,8 +210,29 @@ public class DeviceApiController { public DeferredResult getFirmware(@PathVariable("deviceToken") String deviceToken, @RequestParam(value = "title") String title, @RequestParam(value = "version") String version, - @RequestParam(value = "chunkSize", required = false, defaultValue = "0") int chunkSize, + @RequestParam(value = "size", required = false, defaultValue = "0") int size, @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) { + return getFirmwareCallback(deviceToken, title, version, size, chunk, FirmwareType.FIRMWARE); + } + + @RequestMapping(value = "/{deviceToken}/software", method = RequestMethod.GET) + public DeferredResult getSoftware(@PathVariable("deviceToken") String deviceToken, + @RequestParam(value = "title") String title, + @RequestParam(value = "version") String version, + @RequestParam(value = "size", required = false, defaultValue = "0") int size, + @RequestParam(value = "chunk", required = false, defaultValue = "0") int chunk) { + return getFirmwareCallback(deviceToken, title, version, size, chunk, FirmwareType.SOFTWARE); + } + + @RequestMapping(value = "/provision", method = RequestMethod.POST) + public DeferredResult provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) { + DeferredResult responseWriter = new DeferredResult<>(); + transportContext.getTransportService().process(JsonConverter.convertToProvisionRequestMsg(json), + new DeviceProvisionCallback(responseWriter)); + return responseWriter; + } + + private DeferredResult getFirmwareCallback(String deviceToken, String title, String version, int size, int chunk, FirmwareType firmwareType) { DeferredResult responseWriter = new DeferredResult<>(); transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { @@ -218,20 +240,13 @@ public class DeviceApiController { .setTenantIdMSB(sessionInfo.getTenantIdMSB()) .setTenantIdLSB(sessionInfo.getTenantIdLSB()) .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) - .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()).build(); - transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, title, version, chunkSize, chunk)); + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) + .setType(firmwareType.name()).build(); + transportContext.getTransportService().process(sessionInfo, requestMsg, new GetFirmwareCallback(responseWriter, title, version, size, chunk)); })); return responseWriter; } - @RequestMapping(value = "/provision", method = RequestMethod.POST) - public DeferredResult provisionDevice(@RequestBody String json, HttpServletRequest httpRequest) { - DeferredResult responseWriter = new DeferredResult<>(); - transportContext.getTransportService().process(JsonConverter.convertToProvisionRequestMsg(json), - new DeviceProvisionCallback(responseWriter)); - return responseWriter; - } - private static class DeviceAuthCallback implements TransportServiceCallback { private final TransportContext transportContext; private final DeferredResult responseWriter; 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 5e4d8a75b9..bb1311429c 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 @@ -47,6 +47,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; @@ -59,6 +60,7 @@ import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.service.DefaultTransportService; import org.thingsboard.server.common.transport.service.SessionMetaData; +import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; @@ -68,7 +70,6 @@ import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; import org.thingsboard.server.transport.mqtt.session.MqttTopicMatcher; -import org.thingsboard.server.common.transport.util.SslUtil; import javax.net.ssl.SSLPeerUnverifiedException; import java.io.IOException; @@ -97,6 +98,8 @@ import static io.netty.handler.codec.mqtt.MqttMessageType.UNSUBACK; import static io.netty.handler.codec.mqtt.MqttQoS.AT_LEAST_ONCE; import static io.netty.handler.codec.mqtt.MqttQoS.AT_MOST_ONCE; import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN; /** * @author Andrew Shvayka @@ -104,7 +107,9 @@ import static io.netty.handler.codec.mqtt.MqttQoS.FAILURE; @Slf4j public class MqttTransportHandler extends ChannelInboundHandlerAdapter implements GenericFutureListener>, SessionMsgListener { - private static final Pattern FW_PATTERN = Pattern.compile("v2/fw/request/(?\\d+)/chunk/(?\\d+)"); + private static final Pattern FW_REQUEST_PATTERN = Pattern.compile(DEVICE_FIRMWARE_REQUEST_TOPIC_PATTERN); + private static final Pattern SW_REQUEST_PATTERN = Pattern.compile(DEVICE_SOFTWARE_REQUEST_TOPIC_PATTERN); + private static final String PAYLOAD_TOO_LARGE = "PAYLOAD_TOO_LARGE"; @@ -314,38 +319,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else if (topicName.equals(MqttTopics.DEVICE_CLAIM_TOPIC)) { TransportProtos.ClaimDeviceMsg claimDeviceMsg = payloadAdaptor.convertToClaimDevice(deviceSessionCtx, mqttMsg); transportService.process(deviceSessionCtx.getSessionInfo(), claimDeviceMsg, getPubAckCallback(ctx, msgId, claimDeviceMsg)); - } else if ((fwMatcher = FW_PATTERN.matcher(topicName)).find()) { - String payload = mqttMsg.content().toString(UTF8); - int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0; - String requestId = fwMatcher.group("requestId"); - int chunk = Integer.parseInt(fwMatcher.group("chunk")); - - if (chunkSize > 0) { - this.fwChunkSizes.put(requestId, chunkSize); - } else { - chunkSize = fwChunkSizes.getOrDefault(requestId, 0); - } - - if (chunkSize > context.getMaxPayloadSize()) { - sendFirmwareError(ctx, PAYLOAD_TOO_LARGE); - return; - } - - String firmwareId = fwSessions.get(requestId); - - if (firmwareId != null) { - sendFirmware(ctx, mqttMsg.variableHeader().packetId(), firmwareId, requestId, chunkSize, chunk); - } else { - TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo(); - TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() - .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) - .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) - .setTenantIdMSB(sessionInfo.getTenantIdMSB()) - .setTenantIdLSB(sessionInfo.getTenantIdLSB()) - .build(); - transportService.process(deviceSessionCtx.getSessionInfo(), getFirmwareRequestMsg, - new FirmwareCallback(ctx, mqttMsg.variableHeader().packetId(), getFirmwareRequestMsg, requestId, chunkSize, chunk)); - } + } else if ((fwMatcher = FW_REQUEST_PATTERN.matcher(topicName)).find()) { + getFirmwareCallback(ctx, mqttMsg, msgId, fwMatcher, FirmwareType.FIRMWARE); + } else if ((fwMatcher = SW_REQUEST_PATTERN.matcher(topicName)).find()) { + getFirmwareCallback(ctx, mqttMsg, msgId, fwMatcher, FirmwareType.SOFTWARE); } else { transportService.reportActivity(deviceSessionCtx.getSessionInfo()); ack(ctx, msgId); @@ -357,6 +334,41 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } + private void getFirmwareCallback(ChannelHandlerContext ctx, MqttPublishMessage mqttMsg, int msgId, Matcher fwMatcher, FirmwareType type) { + String payload = mqttMsg.content().toString(UTF8); + int chunkSize = StringUtils.isNotEmpty(payload) ? Integer.parseInt(payload) : 0; + String requestId = fwMatcher.group("requestId"); + int chunk = Integer.parseInt(fwMatcher.group("chunk")); + + if (chunkSize > 0) { + this.fwChunkSizes.put(requestId, chunkSize); + } else { + chunkSize = fwChunkSizes.getOrDefault(requestId, 0); + } + + if (chunkSize > context.getMaxPayloadSize()) { + sendFirmwareError(ctx, PAYLOAD_TOO_LARGE); + return; + } + + String firmwareId = fwSessions.get(requestId); + + if (firmwareId != null) { + sendFirmware(ctx, mqttMsg.variableHeader().packetId(), firmwareId, requestId, chunkSize, chunk, type); + } else { + TransportProtos.SessionInfoProto sessionInfo = deviceSessionCtx.getSessionInfo(); + TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .setType(type.name()) + .build(); + transportService.process(deviceSessionCtx.getSessionInfo(), getFirmwareRequestMsg, + new FirmwareCallback(ctx, msgId, getFirmwareRequestMsg, requestId, chunkSize, chunk)); + } + } + private void ack(ChannelHandlerContext ctx, int msgId) { if (msgId > 0) { ctx.writeAndFlush(createMqttPubAckMsg(msgId)); @@ -435,7 +447,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { FirmwareId firmwareId = new FirmwareId(new UUID(response.getFirmwareIdMSB(), response.getFirmwareIdLSB())); fwSessions.put(requestId, firmwareId.toString()); - sendFirmware(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk); + sendFirmware(ctx, msgId, firmwareId.toString(), requestId, chunkSize, chunk, FirmwareType.valueOf(response.getType())); } else { sendFirmwareError(ctx, response.getResponseStatus().toString()); } @@ -448,13 +460,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } } - private void sendFirmware(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk) { + private void sendFirmware(ChannelHandlerContext ctx, int msgId, String firmwareId, String requestId, int chunkSize, int chunk, FirmwareType type) { log.trace("[{}] Send firmware [{}] to device!", sessionId, firmwareId); ack(ctx, msgId); try { byte[] firmwareChunk = context.getFirmwareDataCache().get(firmwareId, chunkSize, chunk); deviceSessionCtx.getPayloadAdaptor() - .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk) + .convertToPublish(deviceSessionCtx, firmwareChunk, requestId, chunk, type) .ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); if (firmwareChunk != null && chunkSize != firmwareChunk.length) { scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); @@ -504,6 +516,8 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement case MqttTopics.DEVICE_PROVISION_RESPONSE_TOPIC: case MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC: case MqttTopics.DEVICE_FIRMWARE_ERROR_TOPIC: + case MqttTopics.DEVICE_SOFTWARE_RESPONSES_TOPIC: + case MqttTopics.DEVICE_SOFTWARE_ERROR_TOPIC: registerSubQoS(topic, grantedQoSList, reqQoS); break; default: diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java index 8d0bf312a0..dbda48d15d 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/JsonMqttAdaptor.java @@ -30,6 +30,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; @@ -43,6 +44,9 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT; + + /** * @author Andrew Shvayka */ @@ -151,8 +155,8 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { } @Override - public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) { - return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/chunk/" + chunk, firmwareChunk)); + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) { + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); } public static JsonElement validateJsonPayload(UUID sessionId, ByteBuf payloadData) throws AdaptorException { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java index 2bd35df8d9..d0c1f30524 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/MqttTransportAdaptor.java @@ -23,6 +23,7 @@ import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttMessageType; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttPublishVariableHeader; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; @@ -77,7 +78,7 @@ public interface MqttTransportAdaptor { Optional convertToPublish(MqttDeviceAwareSessionContext ctx, ProvisionDeviceResponseMsg provisionResponse) throws AdaptorException; - Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException; + Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) throws AdaptorException; default MqttPublishMessage createMqttPublishMsg(MqttDeviceAwareSessionContext ctx, String topic, byte[] payloadInBytes) { MqttFixedHeader mqttFixedHeader = 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 d42e7d85e8..29df08e9c3 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 @@ -28,6 +28,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.device.profile.MqttTopics; +import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.transport.adaptor.AdaptorException; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.ProtoConverter; @@ -38,6 +39,8 @@ import org.thingsboard.server.transport.mqtt.session.MqttDeviceAwareSessionConte import java.util.Optional; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT; + @Component @Slf4j public class ProtoMqttAdaptor implements MqttTransportAdaptor { @@ -165,8 +168,8 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { } @Override - public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk) throws AdaptorException { - return Optional.of(createMqttPublishMsg(ctx, MqttTopics.DEVICE_FIRMWARE_RESPONSE_TOPIC_PREFIX + requestId + "/" + chunk, firmwareChunk)); + public Optional convertToPublish(MqttDeviceAwareSessionContext ctx, byte[] firmwareChunk, String requestId, int chunk, FirmwareType firmwareType) throws AdaptorException { + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 0a61ce554e..c8d35603ab 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -83,6 +83,8 @@ public interface DeviceDao extends Dao, TenantEntityDao { PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(UUID tenantId, String type, PageLink pageLink); + PageData findDevicesByTenantIdAndTypeAndEmptySoftware(UUID tenantId, String type, PageLink pageLink); + /** * Find device infos by tenantId, type and page link. * diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index 11d2fecd24..811f944a37 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -357,13 +357,22 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Override public PageData findDevicesByTenantIdAndTypeAndEmptyFirmware(TenantId tenantId, String type, PageLink pageLink) { - log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); + log.trace("Executing findDevicesByTenantIdAndTypeAndEmptyFirmware, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateString(type, "Incorrect type " + type); validatePageLink(pageLink); return deviceDao.findDevicesByTenantIdAndTypeAndEmptyFirmware(tenantId.getId(), type, pageLink); } + @Override + public PageData findDevicesByTenantIdAndTypeAndEmptySoftware(TenantId tenantId, String type, PageLink pageLink) { + log.trace("Executing findDevicesByTenantIdAndTypeAndEmptySoftware, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateString(type, "Incorrect type " + type); + validatePageLink(pageLink); + return deviceDao.findDevicesByTenantIdAndTypeAndEmptySoftware(tenantId.getId(), type, pageLink); + } + @Override public PageData findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { log.trace("Executing findDeviceInfosByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index 5b9d49a036..ed6a871fea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -106,7 +106,7 @@ public abstract class AbstractDeviceEntity extends BaseSqlEnti this.firmwareId = device.getFirmwareId().getId(); } if (device.getSoftwareId() != null) { - this.firmwareId = device.getSoftwareId().getId(); + this.softwareId = device.getSoftwareId().getId(); } this.deviceData = JacksonUtil.convertValue(device.getDeviceData(), ObjectNode.class); this.name = device.getName(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index 999e69f330..ac71fee1a8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -103,6 +103,15 @@ public interface DeviceRepository extends PagingAndSortingRepository findByTenantIdAndTypeAndSoftwareIdIsNull(@Param("tenantId") UUID tenantId, + @Param("type") String type, + @Param("textSearch") String textSearch, + Pageable pageable); + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo, p.name) " + "FROM DeviceEntity d " + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java index a545c7cc6b..efdd70f53b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/JpaDeviceDao.java @@ -159,6 +159,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao DaoUtil.toPageable(pageLink))); } + @Override + public PageData findDevicesByTenantIdAndTypeAndEmptySoftware(UUID tenantId, String type, PageLink pageLink) { + return DaoUtil.toPageData( + deviceRepository.findByTenantIdAndTypeAndSoftwareIdIsNull( + tenantId, + type, + Objects.toString(pageLink.getTextSearch(), ""), + DaoUtil.toPageable(pageLink))); + } + @Override public PageData findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { return DaoUtil.toPageData( diff --git a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts b/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts index ccdf16aedd..751415bcce 100644 --- a/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/firmware/firmware-autocomplete.component.ts @@ -183,7 +183,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn this.entityService.getEntity(EntityType.FIRMWARE, firmwareId, {ignoreLoading: true, ignoreErrors: true}).subscribe( (entity) => { this.modelValue = entity.id.id; - this.firmwareFormGroup.get('firmwareId').patchValue(entity, {emitEvent: false}); + this.firmwareFormGroup.get('firmwareId').patchValue(entity); }, () => { this.modelValue = null; @@ -196,6 +196,7 @@ export class FirmwareAutocompleteComponent implements ControlValueAccessor, OnIn } else { this.modelValue = null; this.firmwareFormGroup.get('firmwareId').patchValue('', {emitEvent: false}); + this.propagateChange(null); } } else { this.modelValue = null; From 37891ec6c6f4313fb74f46fc8e83371e56367f65 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 30 Apr 2021 11:14:29 +0300 Subject: [PATCH 115/127] Minor refactoring --- .../firmware/DefaultFirmwareStateService.java | 36 +++++-------------- .../transport/DefaultTransportApiService.java | 21 ++--------- .../server/common/data/Device.java | 2 +- .../server/common/data/DeviceProfile.java | 2 +- .../server/common/data/HasFirmware.java | 25 +++++++++++++ ...FirmwareKeyUtil.java => FirmwareUtil.java} | 19 +++++++++- 6 files changed, 56 insertions(+), 49 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/HasFirmware.java rename common/data/src/main/java/org/thingsboard/server/common/data/firmware/{FirmwareKeyUtil.java => FirmwareUtil.java} (80%) diff --git a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java index a09ac3bb31..ca369b3698 100644 --- a/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/firmware/DefaultFirmwareStateService.java @@ -24,7 +24,7 @@ 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.FirmwareInfo; -import org.thingsboard.server.common.data.firmware.FirmwareKeyUtil; +import org.thingsboard.server.common.data.firmware.FirmwareUtil; import org.thingsboard.server.common.data.firmware.FirmwareType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.FirmwareId; @@ -66,9 +66,9 @@ import static org.thingsboard.server.common.data.firmware.FirmwareKey.STATE; import static org.thingsboard.server.common.data.firmware.FirmwareKey.TITLE; import static org.thingsboard.server.common.data.firmware.FirmwareKey.TS; import static org.thingsboard.server.common.data.firmware.FirmwareKey.VERSION; -import static org.thingsboard.server.common.data.firmware.FirmwareKeyUtil.getAttributeKey; -import static org.thingsboard.server.common.data.firmware.FirmwareKeyUtil.getTargetTelemetryKey; -import static org.thingsboard.server.common.data.firmware.FirmwareKeyUtil.getTelemetryKey; +import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getAttributeKey; +import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTargetTelemetryKey; +import static org.thingsboard.server.common.data.firmware.FirmwareUtil.getTelemetryKey; import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE; @@ -217,30 +217,10 @@ public class DefaultFirmwareStateService implements FirmwareStateService { if (device == null) { log.warn("[{}] [{}] Device was removed during firmware update msg was queued!", tenantId, deviceId); } else { - FirmwareId currentFirmwareId; - - switch (firmwareType) { - case FIRMWARE: - currentFirmwareId = device.getFirmwareId(); - break; - case SOFTWARE: - currentFirmwareId = device.getSoftwareId(); - break; - default: - log.warn("Unsupported firmware type: [{}]", firmwareType); - return false; - } - + FirmwareId currentFirmwareId = FirmwareUtil.getFirmwareId(device, firmwareType); if (currentFirmwareId == null) { DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(tenantId, device.getDeviceProfileId()); - switch (firmwareType) { - case FIRMWARE: - currentFirmwareId = deviceProfile.getFirmwareId(); - break; - case SOFTWARE: - currentFirmwareId = deviceProfile.getSoftwareId(); - break; - } + currentFirmwareId = FirmwareUtil.getFirmwareId(deviceProfile, firmwareType); } if (targetFirmwareId.equals(currentFirmwareId)) { @@ -333,13 +313,13 @@ public class DefaultFirmwareStateService implements FirmwareStateService { } private void remove(Device device, FirmwareType firmwareType) { - telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, FirmwareKeyUtil.getAttributeKeys(firmwareType), + telemetryService.deleteAndNotify(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, FirmwareUtil.getAttributeKeys(firmwareType), new FutureCallback<>() { @Override public void onSuccess(@Nullable Void tmp) { log.trace("[{}] Success remove target firmware attributes!", device.getId()); Set keysToNotify = new HashSet<>(); - FirmwareKeyUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); + FirmwareUtil.ALL_FW_ATTRIBUTE_KEYS.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null); } 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 6a765dff63..28890700e7 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 @@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.device.credentials.BasicMqttCredential import org.thingsboard.server.common.data.device.credentials.ProvisionDeviceCredentialsData; import org.thingsboard.server.common.data.device.profile.ProvisionDeviceProfileCredentials; import org.thingsboard.server.common.data.firmware.FirmwareType; +import org.thingsboard.server.common.data.firmware.FirmwareUtil; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -463,26 +464,10 @@ public class DefaultTransportApiService implements TransportApiService { return getEmptyTransportApiResponseFuture(); } - FirmwareId firmwareId = null; - switch (firmwareType) { - case FIRMWARE: - firmwareId = device.getFirmwareId(); - break; - case SOFTWARE: - firmwareId = device.getSoftwareId(); - break; - } - + FirmwareId firmwareId = FirmwareUtil.getFirmwareId(device, firmwareType); if (firmwareId == null) { DeviceProfile deviceProfile = deviceProfileCache.find(device.getDeviceProfileId()); - switch (firmwareType) { - case FIRMWARE: - firmwareId = deviceProfile.getFirmwareId(); - break; - case SOFTWARE: - firmwareId = deviceProfile.getSoftwareId(); - break; - } + firmwareId = FirmwareUtil.getFirmwareId(deviceProfile, firmwareType); } TransportProtos.GetFirmwareResponseMsg.Builder builder = TransportProtos.GetFirmwareResponseMsg.newBuilder(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java index b48fc114cb..bce3ba703f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java @@ -32,7 +32,7 @@ import java.io.IOException; @EqualsAndHashCode(callSuper = true) @Slf4j -public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId { +public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, HasFirmware { private static final long serialVersionUID = 2807343040519543363L; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index c3a2e5ecee..daf7d77af0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -36,7 +36,7 @@ import static org.thingsboard.server.common.data.SearchTextBasedWithAdditionalIn @Data @EqualsAndHashCode(callSuper = true) @Slf4j -public class DeviceProfile extends SearchTextBased implements HasName, HasTenantId { +public class DeviceProfile extends SearchTextBased implements HasName, HasTenantId, HasFirmware { private TenantId tenantId; @NoXss diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HasFirmware.java b/common/data/src/main/java/org/thingsboard/server/common/data/HasFirmware.java new file mode 100644 index 0000000000..ae05829092 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/HasFirmware.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2021 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 org.thingsboard.server.common.data.id.FirmwareId; + +public interface HasFirmware { + + FirmwareId getFirmwareId(); + + FirmwareId getSoftwareId(); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKeyUtil.java b/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUtil.java similarity index 80% rename from common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKeyUtil.java rename to common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUtil.java index 4712d48430..646ad24173 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareKeyUtil.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/firmware/FirmwareUtil.java @@ -15,6 +15,10 @@ */ package org.thingsboard.server.common.data.firmware; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.HasFirmware; +import org.thingsboard.server.common.data.id.FirmwareId; + import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -22,7 +26,8 @@ import java.util.List; import static org.thingsboard.server.common.data.firmware.FirmwareType.FIRMWARE; import static org.thingsboard.server.common.data.firmware.FirmwareType.SOFTWARE; -public class FirmwareKeyUtil { +@Slf4j +public class FirmwareUtil { public static final List ALL_FW_ATTRIBUTE_KEYS; @@ -71,4 +76,16 @@ public class FirmwareKeyUtil { public static String getTelemetryKey(FirmwareType type, FirmwareKey key) { return type.getKeyPrefix() + "_" + key.getValue(); } + + public static FirmwareId getFirmwareId(HasFirmware entity, FirmwareType firmwareType) { + switch (firmwareType) { + case FIRMWARE: + return entity.getFirmwareId(); + case SOFTWARE: + return entity.getSoftwareId(); + default: + log.warn("Unsupported firmware type: [{}]", firmwareType); + return null; + } + } } From ff334b54389f9a4ceda9ea6e7fa3cb4415b16128 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 30 Apr 2021 14:22:36 +0300 Subject: [PATCH 116/127] Improvements to support large files for firmware updates --- .../server/transport/lwm2m/server/LwM2mNetworkConfig.java | 6 +++--- .../transport/lwm2m/server/LwM2mTransportRequest.java | 2 +- .../transport/lwm2m/server/LwM2mTransportServiceImpl.java | 7 ++++++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java index 22f822bae6..4cf12941a5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java @@ -66,7 +66,7 @@ public class LwM2mNetworkConfig { * A value of {@code 0} turns off transparent handling of blockwise transfers altogether. */ // coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 8192); - coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 16384); + coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024); /** * The default DTLS response matcher. * Supported values are STRICT, RELAXED, or PRINCIPAL. @@ -83,7 +83,7 @@ public class LwM2mNetworkConfig { * The block size (number of bytes) to use when doing a blockwise transfer. \ * This value serves as the upper limit for block size in blockwise transfers */ - coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 512); + coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); /** * The maximum payload size (in bytes) that can be transferred in a * single message, i.e. without requiring a blockwise transfer. @@ -91,7 +91,7 @@ public class LwM2mNetworkConfig { * In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol * DEFAULT_VALUE = 1024 */ - coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 512); + coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index 370abaa9cd..5cf106419c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -424,7 +424,7 @@ public class LwM2mTransportRequest { LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), response.getCode().getName(), request.getPath().toString(), value); serviceImpl.sendLogsToThingsboard(msg, registration.getId()); - log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName().toString(), registration.getEndpoint(), + log.trace("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName().toString(), registration.getEndpoint(), ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), value); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java index ae9055da9e..350aebc4c7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java @@ -334,6 +334,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { String pathName = tsKvProto.getKv().getKey(); String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); + //TODO: react on change of the firmware name. if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { this.getInfoFirmwareUpdate(lwM2MClient); } @@ -612,6 +613,9 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { public void sendLogsToThingsboard(String logMsg, String registrationId) { SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId); if (logMsg != null && sessionInfo != null) { + if(logMsg.length() > 1024){ + logMsg = logMsg.substring(0, 1024); + } this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(this.lwM2mTransportContextServer.getKvLogyToThingsboard(logMsg), sessionInfo); } } @@ -1372,6 +1376,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) .setTenantIdMSB(sessionInfo.getTenantIdMSB()) .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .setType(FirmwareType.FIRMWARE.name()) .build(); transportService.process(sessionInfo, getFirmwareRequestMsg, new TransportServiceCallback<>() { @@ -1416,7 +1421,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { Integer objectId = 5; String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(objectId); String targetIdVer = LWM2M_SEPARATOR_PATH + objectId + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0; - lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.TLV.getName(), + lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(), firmwareChunk, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion()); } From a787ca9d5d933277760dd86d63defed0c8520be8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 30 Apr 2021 16:35:21 +0300 Subject: [PATCH 117/127] Refactoring of the lwm2m yml configuration --- .../server/controller/BaseController.java | 4 +- .../server/controller/Lwm2mController.java | 4 +- ...=> LwM2MServerSecurityInfoRepository.java} | 113 +++------ .../src/main/resources/thingsboard.yml | 60 ++--- ...TransportBootstrapServerConfiguration.java | 51 ++-- .../LwM2MTransportContextBootstrap.java | 12 +- .../secure/LwM2MBootstrapSecurityStore.java | 18 +- .../lwm2m/config/LwM2MSecureServerConfig.java | 38 +++ .../LwM2MTransportBootstrapConfig.java} | 47 ++-- .../config/LwM2MTransportServerConfig.java | 153 ++++++++++++ ...LwM2mCredentialsSecurityInfoValidator.java | 14 +- .../lwm2m/server/LwM2mServerListener.java | 4 +- .../server/LwM2mTransportContextServer.java | 14 +- ...er.java => LwM2mTransportHandlerUtil.java} | 4 +- .../lwm2m/server/LwM2mTransportRequest.java | 30 +-- .../LwM2mTransportServerConfiguration.java | 69 +++--- .../LwM2mTransportServerInitializer.java | 2 +- .../server/LwM2mTransportServiceImpl.java | 94 ++++---- .../lwm2m/server/client/LwM2mClient.java | 6 +- .../server/client/LwM2mClientContextImpl.java | 8 +- .../server/client/Lwm2mClientRpcRequest.java | 4 +- .../credentials/shell/lwM2M_credentials.sh | 0 .../serverKeyStore.jks => lwm2mserver.jks} | Bin .../lwm2m/LwM2MTransportConfigServer.java | 217 ------------------ .../serverKeyStore.jks => lwm2mserver.jks} | Bin 25 files changed, 433 insertions(+), 533 deletions(-) rename application/src/main/java/org/thingsboard/server/service/lwm2m/{LwM2MModelsRepository.java => LwM2MServerSecurityInfoRepository.java} (53%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java rename common/transport/{transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java => lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java} (54%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{LwM2mTransportHandler.java => LwM2mTransportHandlerUtil.java} (99%) mode change 100755 => 100644 common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh rename common/transport/lwm2m/src/main/resources/{credentials/serverKeyStore.jks => lwm2mserver.jks} (100%) delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java rename transport/lwm2m/src/main/data/{credentials/serverKeyStore.jks => lwm2mserver.jks} (100%) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 60a71a60ac..505416f4d6 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -132,7 +132,7 @@ import org.thingsboard.server.service.firmware.FirmwareStateService; import org.thingsboard.server.service.edge.EdgeNotificationService; import org.thingsboard.server.service.edge.rpc.EdgeGrpcService; import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService; -import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository; +import org.thingsboard.server.service.lwm2m.LwM2MServerSecurityInfoRepository; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.resource.TbResourceService; @@ -267,7 +267,7 @@ public abstract class BaseController { protected TbDeviceProfileCache deviceProfileCache; @Autowired - protected LwM2MModelsRepository lwM2MModelsRepository; + protected LwM2MServerSecurityInfoRepository lwM2MServerSecurityInfoRepository; @Autowired(required = false) protected EdgeService edgeService; diff --git a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java index 02605ffaf0..0855e4ee1f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -47,9 +47,9 @@ public class Lwm2mController extends BaseController { @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET) @ResponseBody public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode, - @PathVariable("bootstrapServerIs") boolean bootstrapServerIs) throws ThingsboardException { + @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException { try { - return lwM2MModelsRepository.getBootstrapSecurityInfo(securityMode, bootstrapServerIs); + return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java similarity index 53% rename from application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java rename to application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java index 51ae94ba2e..012cd3e359 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MModelsRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java @@ -16,14 +16,15 @@ package org.thingsboard.server.service.lwm2m; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.util.Hex; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; -import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap; -import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer; +import org.thingsboard.server.transport.lwm2m.config.LwM2MSecureServerConfig; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import java.math.BigInteger; @@ -42,96 +43,59 @@ import java.security.spec.KeySpec; @Slf4j @Service +@RequiredArgsConstructor @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") -public class LwM2MModelsRepository { +public class LwM2MServerSecurityInfoRepository { - private static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - - @Autowired - LwM2MTransportConfigServer contextServer; - - - @Autowired - LwM2MTransportConfigBootstrap contextBootStrap; + private final LwM2MTransportServerConfig serverConfig; + private final LwM2MTransportBootstrapConfig bootstrapConfig; /** * @param securityMode - * @param bootstrapServerIs + * @param bootstrapServer * @return ServerSecurityConfig more value is default: Important - port, host, publicKey */ - public ServerSecurityConfig getBootstrapSecurityInfo(String securityMode, boolean bootstrapServerIs) { + public ServerSecurityConfig getServerSecurityInfo(String securityMode, boolean bootstrapServer) { LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase()); - return getBootstrapServer(bootstrapServerIs, lwM2MSecurityMode); + ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, lwM2MSecurityMode); + result.setBootstrapServerIs(bootstrapServer); + return result; } - /** - * @param bootstrapServerIs - * @param mode - * @return ServerSecurityConfig more value is default: Important - port, host, publicKey - */ - private ServerSecurityConfig getBootstrapServer(boolean bootstrapServerIs, LwM2MSecurityMode mode) { + private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) { ServerSecurityConfig bsServ = new ServerSecurityConfig(); - bsServ.setBootstrapServerIs(bootstrapServerIs); - if (bootstrapServerIs) { - bsServ.setServerId(contextBootStrap.getBootstrapServerId()); - switch (mode) { - case NO_SEC: - bsServ.setHost(contextBootStrap.getBootstrapHost()); - bsServ.setPort(contextBootStrap.getBootstrapPortNoSec()); - bsServ.setServerPublicKey(""); - break; - case PSK: - bsServ.setHost(contextBootStrap.getBootstrapHostSecurity()); - bsServ.setPort(contextBootStrap.getBootstrapPortSecurity()); - bsServ.setServerPublicKey(""); - break; - case RPK: - case X509: - bsServ.setHost(contextBootStrap.getBootstrapHostSecurity()); - bsServ.setPort(contextBootStrap.getBootstrapPortSecurity()); - bsServ.setServerPublicKey(getPublicKey (contextBootStrap.getBootstrapAlias(), this.contextBootStrap.getBootstrapPublicX(), this.contextBootStrap.getBootstrapPublicY())); - break; - default: - break; - } - } else { - bsServ.setServerId(contextServer.getServerId()); - switch (mode) { - case NO_SEC: - bsServ.setHost(contextServer.getServerHost()); - bsServ.setPort(contextServer.getServerPortNoSec()); - bsServ.setServerPublicKey(""); - break; - case PSK: - bsServ.setHost(contextServer.getServerHostSecurity()); - bsServ.setPort(contextServer.getServerPortSecurity()); - bsServ.setServerPublicKey(""); - break; - case RPK: - case X509: - bsServ.setHost(contextServer.getServerHostSecurity()); - bsServ.setPort(contextServer.getServerPortSecurity()); - bsServ.setServerPublicKey(getPublicKey (contextServer.getServerAlias(), this.contextServer.getServerPublicX(), this.contextServer.getServerPublicY())); - break; - default: - break; - } + bsServ.setServerId(serverConfig.getId()); + switch (mode) { + case NO_SEC: + bsServ.setHost(serverConfig.getHost()); + bsServ.setPort(serverConfig.getPort()); + bsServ.setServerPublicKey(""); + break; + case PSK: + bsServ.setHost(serverConfig.getSecureHost()); + bsServ.setPort(serverConfig.getSecurePort()); + bsServ.setServerPublicKey(""); + break; + case RPK: + case X509: + bsServ.setHost(serverConfig.getSecureHost()); + bsServ.setPort(serverConfig.getSecurePort()); + bsServ.setServerPublicKey(getPublicKey(serverConfig.getCertificateAlias(), this.serverConfig.getPublicX(), this.serverConfig.getPublicY())); + break; + default: + break; } return bsServ; } - private String getPublicKey (String alias, String publicServerX, String publicServerY) { + private String getPublicKey(String alias, String publicServerX, String publicServerY) { String publicKey = getServerPublicKeyX509(alias); return publicKey != null ? publicKey : getRPKPublicKey(publicServerX, publicServerY); } - /** - * @param alias - * @return PublicKey format HexString or null - */ private String getServerPublicKeyX509(String alias) { try { - X509Certificate serverCertificate = (X509Certificate) contextServer.getKeyStoreValue().getCertificate(alias); + X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(alias); return Hex.encodeHexString(serverCertificate.getEncoded()); } catch (CertificateEncodingException | KeyStoreException e) { e.printStackTrace(); @@ -139,11 +103,6 @@ public class LwM2MModelsRepository { return null; } - /** - * @param publicServerX - * @param publicServerY - * @return PublicKey format HexString or null - */ private String getRPKPublicKey(String publicServerX, String publicServerY) { try { /** Get Elliptic Curve Parameter spec for secp256r1 */ diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 33760d174c..83f1c8fa02 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -630,38 +630,14 @@ transport: lwm2m: # Enable/disable lvm2m transport protocol. enabled: "${LWM2M_ENABLED:true}" - # We choose a default timeout a bit higher to the MAX_TRANSMIT_WAIT(62-93s) which is the time from starting to - # send a Confirmable message to the time when an acknowledgement is no longer expected. - # DEFAULT_TIMEOUT = 2 * 60 * 1000l; 2 min in ms - timeout: "${LWM2M_TIMEOUT:120000}" - recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" - recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" - response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}" - registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" - update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" - un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" - secure: - # Certificate_x509: - # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format - # Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh - key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}" - # key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks" - key_store_path_file: "${KEY_STORE_PATH_FILE:}" - key_store_password: "${LWM2M_KEYSTORE_PASSWORD_SERVER:server_ks_password}" - root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}" - enable_gen_new_key_psk_rpk: "${ENABLE_GEN_NEW_KEY_PSK_RPK:false}" server: id: "${LWM2M_SERVER_ID:123}" bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" - bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC:5685}" - secure: - bind_address_security: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}" - bind_port_security: "${LWM2M_BIND_PORT_SECURITY:5686}" - # create_rpk: "${CREATE_RPK:}" + bind_port: "${LWM2M_BIND_PORT:5685}" + security: + bind_address: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}" + bind_port: "${LWM2M_BIND_PORT_SECURITY:5686}" # Only for RPK: Public & Private Key. If the keystore file is missing or not working - # - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d0301070342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b] - # - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b], - # - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)] public_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}" public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" @@ -671,19 +647,33 @@ transport: enable: "${LWM2M_ENABLED_BS:true}" id: "${LWM2M_SERVER_ID_BS:111}" bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port_no_sec: "${LWM2M_BIND_PORT_NO_SEC_BS:5687}" - secure: - bind_address_security: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port_security: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" + bind_port: "${LWM2M_BIND_PORT_BS:5687}" + security: + bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" + bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" # Only for RPK: Public & Private Key. If the keystore file is missing or not working - # - Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)] - # - Public Key (Hex): [3059301306072a8648ce3d020106082a8648ce3d030107034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34] - # - Private Key (Hex): [308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34], public_x: "${LWM2M_SERVER_PUBLIC_X_BS:5017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f91}" public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:3fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:308193020100301306072a8648ce3d020106082a8648ce3d0301070479307702010104205ecafd90caa7be45c42e1f3f32571632b8409e6e6249d7124f4ba56fab3c8083a00a06082a8648ce3d030107a144034200045017c87a1c1768264656b3b355434b0def6edb8b9bf166a4762d9930cd730f913fc4e61bcd8901ec27c424114c3e887ed372497f0c2cf85839b8443e76988b34}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_BS:bootstrap}" + security: + # Certificate_x509: + # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format + # Create new X509 Certificates: common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh + key_store_type: "${LWM2M_KEYSTORE_TYPE:JKS}" + # key_store_path_file: "${KEY_STORE_PATH_FILE:/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks" + key_store: "${LWM2M_KEY_STORE:lwm2mserver.jks}" + key_store_password: "${LWM2M_KEY_STORE_PASSWORD:server_ks_password}" + root_alias: "${LWM2M_SERVER_ROOT_CA:rootca}" + enable_gen_new_key_psk_rpk: "${ENABLE_GEN_NEW_KEY_PSK_RPK:false}" + timeout: "${LWM2M_TIMEOUT:120000}" + recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" + recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" + response_pool_size: "${LWM2M_RESPONSE_POOL_SIZE:100}" + registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" + update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" + un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" # Use redis for Security and Registration stores redis.enabled: "${LWM2M_REDIS_ENABLED:false}" snmp: diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java index 9e40c736df..2c0e01d169 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java @@ -25,9 +25,11 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; import java.math.BigInteger; @@ -81,13 +83,13 @@ public class LwM2MTransportBootstrapServerConfiguration { @Bean public LeshanBootstrapServer getLeshanBootstrapServer() { log.info("Prepare and start BootstrapServer... PostConstruct"); - return this.getLhBootstrapServer(this.contextBs.getCtxBootStrap().getBootstrapPortNoSec(), this.contextBs.getCtxBootStrap().getBootstrapPortSecurity()); + return this.getLhBootstrapServer(this.contextBs.getCtxBootStrap().getPort(), this.contextBs.getCtxBootStrap().getSecurePort()); } public LeshanBootstrapServer getLhBootstrapServer(Integer bootstrapPortNoSec, Integer bootstrapSecurePort) { LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); - builder.setLocalAddress(this.contextBs.getCtxBootStrap().getBootstrapHost(), bootstrapPortNoSec); - builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getBootstrapHostSecurity(), bootstrapSecurePort); + builder.setLocalAddress(this.contextBs.getCtxBootStrap().getHost(), bootstrapPortNoSec); + builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getSecureHost(), bootstrapSecurePort); /** Create CoAP Config */ builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort)); @@ -106,14 +108,13 @@ public class LwM2MTransportBootstrapServerConfiguration { /** Create and Set DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); - dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getLwM2MTransportConfigServer().isRecommendedSupportedGroups()); - dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getLwM2MTransportConfigServer().isRecommendedCiphers()); + dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getLwM2MTransportServerConfig().isRecommendedSupportedGroups()); + dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getLwM2MTransportServerConfig().isRecommendedCiphers()); if (this.pskMode) { dtlsConfig.setSupportedCipherSuites( TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256); - } - else { + } else { dtlsConfig.setSupportedCipherSuites( TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256, @@ -133,10 +134,10 @@ public class LwM2MTransportBootstrapServerConfiguration { private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) { try { - if (this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue() != null) { - KeyStore keyStoreServer = this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue(); + if (this.contextS.getLwM2MTransportServerConfig().getKeyStoreValue() != null) { + KeyStore keyStoreServer = this.contextS.getLwM2MTransportServerConfig().getKeyStoreValue(); if (this.setBuilderX509(builder)) { - X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getLwM2MTransportConfigServer().getRootAlias()); + X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getLwM2MTransportServerConfig().getRootCertificateAlias()); if (rootCAX509Cert != null) { X509Certificate[] trustedCertificates = new X509Certificate[1]; trustedCertificates[0] = rootCAX509Cert; @@ -167,12 +168,10 @@ public class LwM2MTransportBootstrapServerConfiguration { * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks */ try { - X509Certificate serverCertificate = (X509Certificate) this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getBootstrapAlias()); - PrivateKey privateKey = (PrivateKey) this.contextS.getLwM2MTransportConfigServer().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getBootstrapAlias(), this.contextS.getLwM2MTransportConfigServer().getKeyStorePasswordServer() == null ? null : this.contextS.getLwM2MTransportConfigServer().getKeyStorePasswordServer().toCharArray()); + X509Certificate serverCertificate = (X509Certificate) this.contextS.getLwM2MTransportServerConfig().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getCertificateAlias()); + PrivateKey privateKey = (PrivateKey) this.contextS.getLwM2MTransportServerConfig().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getCertificateAlias(), this.contextS.getLwM2MTransportServerConfig().getKeyStorePassword() == null ? null : this.contextS.getLwM2MTransportServerConfig().getKeyStorePassword().toCharArray()); PublicKey publicKey = serverCertificate.getPublicKey(); - if (serverCertificate != null && - privateKey != null && privateKey.getEncoded().length > 0 && - publicKey != null && publicKey.getEncoded().length > 0) { + if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) { builder.setPublicKey(serverCertificate.getPublicKey()); builder.setPrivateKey(privateKey); builder.setCertificateChain(new X509Certificate[]{serverCertificate}); @@ -199,10 +198,12 @@ public class LwM2MTransportBootstrapServerConfiguration { } private void infoPramsUri(String mode) { - log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}], serverSecureURI : [{}]", + log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]", mode, - this.contextBs.getCtxBootStrap().getBootstrapHost() + ":" + this.contextBs.getCtxBootStrap().getBootstrapPortNoSec(), - this.contextBs.getCtxBootStrap().getBootstrapHostSecurity() + ":" + this.contextBs.getCtxBootStrap().getBootstrapPortSecurity()); + this.contextBs.getCtxBootStrap().getHost(), + this.contextBs.getCtxBootStrap().getPort(), + this.contextBs.getCtxBootStrap().getSecureHost(), + this.contextBs.getCtxBootStrap().getSecurePort()); } @@ -236,23 +237,25 @@ public class LwM2MTransportBootstrapServerConfiguration { AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); algoParameters.init(new ECGenParameterSpec("secp256r1")); ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (this.contextBs.getCtxBootStrap().getBootstrapPublicX() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicX().isEmpty() && this.contextBs.getCtxBootStrap().getBootstrapPublicY() != null && !this.contextBs.getCtxBootStrap().getBootstrapPublicY().isEmpty()) { + LwM2MTransportBootstrapConfig serverConfig = this.contextBs.getCtxBootStrap(); + if (StringUtils.isNotEmpty(serverConfig.getPublicX()) && StringUtils.isNotEmpty(serverConfig.getPublicY())) { /** Get point values */ - byte[] publicX = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicX().toCharArray()); - byte[] publicY = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPublicY().toCharArray()); + byte[] publicX = Hex.decodeHex(serverConfig.getPublicX().toCharArray()); + byte[] publicY = Hex.decodeHex(serverConfig.getPublicY().toCharArray()); /** Create key specs */ KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), parameterSpec); /** Get public key */ this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); } - if (this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded() != null && !this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded().isEmpty()) { + String privateEncodedKey = serverConfig.getPrivateEncoded(); + if (StringUtils.isNotEmpty(privateEncodedKey)) { /** Get private key */ - byte[] privateS = Hex.decodeHex(this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded().toCharArray()); + byte[] privateS = Hex.decodeHex(privateEncodedKey.toCharArray()); try { this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS)); } catch (InvalidKeySpecException ignore2) { - log.error("Invalid Bootstrap Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", this.contextBs.getCtxBootStrap().getBootstrapPrivateEncoded()); + log.error("Invalid Bootstrap Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", privateEncodedKey); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java index 43e3781716..35dff0181d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java @@ -34,7 +34,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; import org.thingsboard.server.common.transport.TransportContext; -import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstrap; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; @Slf4j @@ -42,13 +42,13 @@ import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigBootstr @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith'") public class LwM2MTransportContextBootstrap extends TransportContext { - private final LwM2MTransportConfigBootstrap lwM2MTransportConfigBootstrap; + private final LwM2MTransportBootstrapConfig lwM2MTransportBootstrapConfig; - public LwM2MTransportContextBootstrap(LwM2MTransportConfigBootstrap ctxBootStrap) { - this.lwM2MTransportConfigBootstrap = ctxBootStrap; + public LwM2MTransportContextBootstrap(LwM2MTransportBootstrapConfig ctxBootStrap) { + this.lwM2MTransportBootstrapConfig = ctxBootStrap; } - public LwM2MTransportConfigBootstrap getCtxBootStrap() { - return this.lwM2MTransportConfigBootstrap; + public LwM2MTransportBootstrapConfig getCtxBootStrap() { + return this.lwM2MTransportBootstrapConfig; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 306603cf78..7aefe19da5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -35,7 +35,7 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInf import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil; import java.io.IOException; import java.security.GeneralSecurityException; @@ -43,12 +43,12 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.BOOTSTRAP_SERVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_SERVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVERS; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getBootstrapParametersFromThingsboard; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.BOOTSTRAP_SERVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_SERVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVERS; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getBootstrapParametersFromThingsboard; @Slf4j @Service("LwM2MBootstrapSecurityStore") @@ -69,7 +69,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { /** add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); @@ -93,7 +93,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { /** add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java new file mode 100644 index 0000000000..fcca9fb975 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MSecureServerConfig.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2021 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.lwm2m.config; + +public interface LwM2MSecureServerConfig { + + Integer getId(); + + String getHost(); + + Integer getPort(); + + String getSecureHost(); + + Integer getSecurePort(); + + String getPublicX(); + + String getPublicY(); + + String getPrivateEncoded(); + + String getCertificateAlias(); + +} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java similarity index 54% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java index 14da88ecd1..ea4f7ad391 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.transport.lwm2m; +package org.thingsboard.server.transport.lwm2m.config; import lombok.Getter; import lombok.Setter; @@ -29,53 +29,42 @@ import java.util.Map; @Slf4j @Component @ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") -public class LwM2MTransportConfigBootstrap { - - @Getter - @Value("${transport.lwm2m.bootstrap.enable:}") - private Boolean bootstrapEnable; +public class LwM2MTransportBootstrapConfig implements LwM2MSecureServerConfig { @Getter @Value("${transport.lwm2m.bootstrap.id:}") - private Integer bootstrapServerId; + private Integer id; @Getter @Value("${transport.lwm2m.bootstrap.bind_address:}") - private String bootstrapHost; + private String host; @Getter - @Value("${transport.lwm2m.bootstrap.bind_port_no_sec:}") - private Integer bootstrapPortNoSec; + @Value("${transport.lwm2m.bootstrap.bind_port:}") + private Integer port; @Getter - @Value("${transport.lwm2m.bootstrap.secure.bind_address_security:}") - private String bootstrapHostSecurity; + @Value("${transport.lwm2m.bootstrap.security.bind_address:}") + private String secureHost; @Getter - @Value("${transport.lwm2m.bootstrap.secure.bind_port_security:}") - private Integer bootstrapPortSecurity; + @Value("${transport.lwm2m.bootstrap.security.bind_port:}") + private Integer securePort; @Getter - @Value("${transport.lwm2m.bootstrap.secure.public_x:}") - private String bootstrapPublicX; + @Value("${transport.lwm2m.bootstrap.security.public_x:}") + private String publicX; @Getter - @Value("${transport.lwm2m.bootstrap.secure.public_y:}") - private String bootstrapPublicY; + @Value("${transport.lwm2m.bootstrap.security.public_y:}") + private String publicY; @Getter - @Setter - private PublicKey bootstrapPublicKey; + @Value("${transport.lwm2m.bootstrap.security.private_encoded:}") + private String privateEncoded; @Getter - @Value("${transport.lwm2m.bootstrap.secure.private_encoded:}") - private String bootstrapPrivateEncoded; + @Value("${transport.lwm2m.bootstrap.security.alias:}") + private String certificateAlias; - @Getter - @Value("${transport.lwm2m.bootstrap.secure.alias:}") - private String bootstrapAlias; - - @Getter - @Setter - private Map sessions; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java new file mode 100644 index 0000000000..a68359a1ca --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -0,0 +1,153 @@ +/** + * Copyright © 2016-2021 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.lwm2m.config; + +import com.google.common.io.Resources; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.server.model.LwM2mModelProvider; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateException; + +@Slf4j +@Component +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") +public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { + + @Getter + @Setter + private LwM2mModelProvider modelProvider; + + @Getter + @Value("${transport.lwm2m.timeout:}") + private Long timeout; + + @Getter + @Value("${transport.sessions.report_timeout}") + private long sessionReportTimeout; + + @Getter + @Value("${transport.lwm2m.recommended_ciphers:}") + private boolean recommendedCiphers; + + @Getter + @Value("${transport.lwm2m.recommended_supported_groups:}") + private boolean recommendedSupportedGroups; + + @Getter + @Value("${transport.lwm2m.response_pool_size:}") + private int responsePoolSize; + + @Getter + @Value("${transport.lwm2m.registered_pool_size:}") + private int registeredPoolSize; + + @Getter + @Value("${transport.lwm2m.update_registered_pool_size:}") + private int updateRegisteredPoolSize; + + @Getter + @Value("${transport.lwm2m.un_registered_pool_size:}") + private int unRegisteredPoolSize; + + @Getter + @Value("${transport.lwm2m.security.key_store_type:}") + private String keyStoreType; + + @Getter + @Value("${transport.lwm2m.security.key_store:}") + private String keyStorePathFile; + + @Getter + @Setter + private KeyStore keyStoreValue; + + @Getter + @Value("${transport.lwm2m.security.key_store_password:}") + private String keyStorePassword; + + @Getter + @Value("${transport.lwm2m.security.root_alias:}") + private String rootCertificateAlias; + + @Getter + @Value("${transport.lwm2m.security.enable_gen_new_key_psk_rpk:}") + private Boolean enableGenNewKeyPskRpk; + + @Getter + @Value("${transport.lwm2m.server.id:}") + private Integer id; + + @Getter + @Value("${transport.lwm2m.server.bind_address:}") + private String host; + + @Getter + @Value("${transport.lwm2m.server.bind_port:}") + private Integer port; + + @Getter + @Value("${transport.lwm2m.server.security.bind_address:}") + private String secureHost; + + @Getter + @Value("${transport.lwm2m.server.security.bind_port:}") + private Integer securePort; + + @Getter + @Value("${transport.lwm2m.server.security.public_x:}") + private String publicX; + + @Getter + @Value("${transport.lwm2m.server.security.public_y:}") + private String publicY; + + @Getter + @Value("${transport.lwm2m.server.security.private_encoded:}") + private String privateEncoded; + + @Getter + @Value("${transport.lwm2m.server.security.alias:}") + private String certificateAlias; + + + @PostConstruct + public void init() { + try { + File keyStoreFile = new File(Resources.getResource(keyStorePathFile).toURI()); + InputStream inKeyStore = new FileInputStream(keyStoreFile); + keyStoreValue = KeyStore.getInstance(keyStoreType); + keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray()); + } catch (Exception e) { + log.error("Unable to lookup LwM2M keystore. Reason: " + e.getMessage(), e); + throw new RuntimeException("Failed to lookup LwM2M keystore", e); + } + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index bc8eb51b45..e9a09c54d3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -27,7 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCreden import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil; import java.io.IOException; import java.security.GeneralSecurityException; @@ -58,7 +58,7 @@ public class LwM2mCredentialsSecurityInfoValidator { * @param keyValue - * @return ValidateDeviceCredentialsResponseMsg and SecurityInfo */ - public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportHandler.LwM2mTypeServer keyValue) { + public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportHandlerUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), @@ -68,7 +68,7 @@ public class LwM2mCredentialsSecurityInfoValidator { String credentialsBody = msg.getCredentialsBody(); resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); - Optional deviceProfileOpt = LwM2mTransportHandler.decode(msg.getProfileBody().toByteArray()); + Optional deviceProfileOpt = LwM2mTransportHandlerUtil.decode(msg.getProfileBody().toByteArray()); deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile)); latch.countDown(); } @@ -81,7 +81,7 @@ public class LwM2mCredentialsSecurityInfoValidator { } }); try { - latch.await(contextS.getLwM2MTransportConfigServer().getTimeout(), TimeUnit.MILLISECONDS); + latch.await(contextS.getLwM2MTransportServerConfig().getTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("Failed to await credentials!", e); } @@ -95,9 +95,9 @@ public class LwM2mCredentialsSecurityInfoValidator { * @param keyValue - * @return SecurityInfo */ - private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, LwM2mTransportHandler.LwM2mTypeServer keyValue) { + private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, LwM2mTransportHandlerUtil.LwM2mTypeServer keyValue) { ReadResultSecurityStore result = new ReadResultSecurityStore(); - JsonObject objectMsg = LwM2mTransportHandler.validateJson(jsonStr); + JsonObject objectMsg = LwM2mTransportHandlerUtil.validateJson(jsonStr); if (objectMsg != null && !objectMsg.isJsonNull()) { JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null; /** @@ -108,7 +108,7 @@ public class LwM2mCredentialsSecurityInfoValidator { && objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null; endPoint = (endPointPsk == null || endPointPsk.isEmpty()) ? endPoint : endPointPsk; if (object != null && !object.isJsonNull()) { - if (keyValue.equals(LwM2mTransportHandler.LwM2mTypeServer.BOOTSTRAP)) { + if (keyValue.equals(LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP)) { result.setBootstrapJsonCredential(object); result.setEndPoint(endPoint); result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index 72626f1103..c8108b10f5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -26,8 +26,8 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate; import java.util.Collection; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer; @Slf4j public class LwM2mServerListener { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java index d3ccf0e86d..e1a322f646 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java @@ -43,7 +43,7 @@ import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.common.transport.lwm2m.LwM2MTransportConfigServer; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; @@ -57,7 +57,7 @@ import java.util.ArrayList; import java.util.List; import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_TELEMETRY; @Slf4j @Component @@ -65,7 +65,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle public class LwM2mTransportContextServer extends TransportContext { - private final LwM2MTransportConfigServer lwM2MTransportConfigServer; + private final LwM2MTransportServerConfig lwM2MTransportServerConfig; private final TransportService transportService; @@ -75,15 +75,15 @@ public class LwM2mTransportContextServer extends TransportContext { @Getter private final LwM2MJsonAdaptor adaptor; - public LwM2mTransportContextServer(LwM2MTransportConfigServer lwM2MTransportConfigServer, TransportService transportService, TransportResourceCache transportResourceCache, LwM2MJsonAdaptor adaptor) { - this.lwM2MTransportConfigServer = lwM2MTransportConfigServer; + public LwM2mTransportContextServer(LwM2MTransportServerConfig lwM2MTransportServerConfig, TransportService transportService, TransportResourceCache transportResourceCache, LwM2MJsonAdaptor adaptor) { + this.lwM2MTransportServerConfig = lwM2MTransportServerConfig; this.transportService = transportService; this.transportResourceCache = transportResourceCache; this.adaptor = adaptor; } - public LwM2MTransportConfigServer getLwM2MTransportConfigServer() { - return this.lwM2MTransportConfigServer; + public LwM2MTransportServerConfig getLwM2MTransportServerConfig() { + return this.lwM2MTransportServerConfig; } public TransportResourceCache getTransportResourceCache() { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java similarity index 99% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java index 08e46eb288..a5780d9879 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java @@ -67,7 +67,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; @Slf4j -public class LwM2mTransportHandler { +public class LwM2mTransportHandlerUtil { public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0"; public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; @@ -256,7 +256,7 @@ public class LwM2mTransportHandler { ObjectMapper mapper = new ObjectMapper(); String profileStr = mapper.writeValueAsString(profile); JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; - return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandler.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; + return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandlerUtil.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; } catch (IOException e) { log.error("", e); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index 5cf106419c..a49e9dbb23 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -66,18 +66,18 @@ import java.util.stream.Collectors; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEFAULT_TIMEOUT; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.FR_PATH_RESOURCE_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_CANCEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_READ_ALL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.RESPONSE_CHANNEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.createWriteAttributeRequest; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEFAULT_TIMEOUT; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_READ_ALL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.RESPONSE_CHANNEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.createWriteAttributeRequest; @Slf4j @Service @@ -110,7 +110,7 @@ public class LwM2mTransportRequest { @PostConstruct public void init() { this.converter = LwM2mValueConverterImpl.getInstance(); - executorResponse = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getResponsePoolSize(), + executorResponse = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getResponsePoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL))); } @@ -161,7 +161,7 @@ public class LwM2mTransportRequest { leshanServer.getObservationService().cancelObservations(registration, target); break; case EXECUTE: - resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() .getModelProvider()); if (params != null && !resourceModel.multiple) { request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModel.type, ResourceModel.Type.STRING, resultIds)); @@ -171,7 +171,7 @@ public class LwM2mTransportRequest { break; case WRITE_REPLACE: // Request to write a String Single-Instance Resource using the TLV content format. - resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() .getModelProvider()); if (contentFormat.equals(ContentFormat.TLV)) { request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java index 86bec24984..e320e3fcfa 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java @@ -31,7 +31,9 @@ import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.SecurityChecker; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -83,13 +85,13 @@ public class LwM2mTransportServerConfiguration { @Bean public LeshanServer getLeshanServer() { log.info("Starting LwM2M transport Server... PostConstruct"); - return this.getLhServer(this.context.getLwM2MTransportConfigServer().getServerPortNoSec(), this.context.getLwM2MTransportConfigServer().getServerPortSecurity()); + return this.getLhServer(this.context.getLwM2MTransportServerConfig().getPort(), this.context.getLwM2MTransportServerConfig().getSecurePort()); } private LeshanServer getLhServer(Integer serverPortNoSec, Integer serverSecurePort) { LeshanServerBuilder builder = new LeshanServerBuilder(); - builder.setLocalAddress(this.context.getLwM2MTransportConfigServer().getServerHost(), serverPortNoSec); - builder.setLocalSecureAddress(this.context.getLwM2MTransportConfigServer().getServerHostSecurity(), serverSecurePort); + builder.setLocalAddress(this.context.getLwM2MTransportServerConfig().getHost(), serverPortNoSec); + builder.setLocalSecureAddress(this.context.getLwM2MTransportServerConfig().getSecureHost(), serverSecurePort); builder.setDecoder(new DefaultLwM2mNodeDecoder()); /** Use a magic converter to support bad type send by the UI. */ builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); @@ -102,7 +104,7 @@ public class LwM2mTransportServerConfiguration { /** Define model provider (Create Models )*/ LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.context); - this.context.getLwM2MTransportConfigServer().setModelProvider(modelProvider); + this.context.getLwM2MTransportServerConfig().setModelProvider(modelProvider); builder.setObjectModelProvider(modelProvider); /** Create credentials */ @@ -116,8 +118,8 @@ public class LwM2mTransportServerConfiguration { /** Create DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setServerOnly(true); - dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getLwM2MTransportConfigServer().isRecommendedSupportedGroups()); - dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getLwM2MTransportConfigServer().isRecommendedCiphers()); + dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getLwM2MTransportServerConfig().isRecommendedSupportedGroups()); + dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getLwM2MTransportServerConfig().isRecommendedCiphers()); if (this.pskMode) { dtlsConfig.setSupportedCipherSuites( TLS_PSK_WITH_AES_128_CCM_8, @@ -139,9 +141,9 @@ public class LwM2mTransportServerConfiguration { private void setServerWithCredentials(LeshanServerBuilder builder) { try { - if (this.context.getLwM2MTransportConfigServer().getKeyStoreValue() != null) { + if (this.context.getLwM2MTransportServerConfig().getKeyStoreValue() != null) { if (this.setBuilderX509(builder)) { - X509Certificate rootCAX509Cert = (X509Certificate) this.context.getLwM2MTransportConfigServer().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportConfigServer().getRootAlias()); + X509Certificate rootCAX509Cert = (X509Certificate) this.context.getLwM2MTransportServerConfig().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportServerConfig().getRootCertificateAlias()); if (rootCAX509Cert != null) { X509Certificate[] trustedCertificates = new X509Certificate[1]; trustedCertificates[0] = rootCAX509Cert; @@ -175,17 +177,11 @@ public class LwM2mTransportServerConfiguration { } private boolean setBuilderX509(LeshanServerBuilder builder) { - /** - * For deb => KeyStorePathFile == yml or commandline: KEY_STORE_PATH_FILE - * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks - */ try { - X509Certificate serverCertificate = (X509Certificate) this.context.getLwM2MTransportConfigServer().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportConfigServer().getServerAlias()); - PrivateKey privateKey = (PrivateKey) this.context.getLwM2MTransportConfigServer().getKeyStoreValue().getKey(this.context.getLwM2MTransportConfigServer().getServerAlias(), this.context.getLwM2MTransportConfigServer().getKeyStorePasswordServer() == null ? null : this.context.getLwM2MTransportConfigServer().getKeyStorePasswordServer().toCharArray()); + X509Certificate serverCertificate = (X509Certificate) this.context.getLwM2MTransportServerConfig().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportServerConfig().getCertificateAlias()); + PrivateKey privateKey = (PrivateKey) this.context.getLwM2MTransportServerConfig().getKeyStoreValue().getKey(this.context.getLwM2MTransportServerConfig().getCertificateAlias(), this.context.getLwM2MTransportServerConfig().getKeyStorePassword() == null ? null : this.context.getLwM2MTransportServerConfig().getKeyStorePassword().toCharArray()); PublicKey publicKey = serverCertificate.getPublicKey(); - if (serverCertificate != null && - privateKey != null && privateKey.getEncoded().length > 0 && - publicKey != null && publicKey.getEncoded().length > 0) { + if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) { builder.setPublicKey(serverCertificate.getPublicKey()); builder.setPrivateKey(privateKey); builder.setCertificateChain(new X509Certificate[]{serverCertificate}); @@ -212,10 +208,12 @@ public class LwM2mTransportServerConfiguration { } private void infoPramsUri(String mode) { - log.info("Server uses [{}]: serverNoSecureURI : [{}], serverSecureURI : [{}]", - mode, - this.context.getLwM2MTransportConfigServer().getServerHost() + ":" + this.context.getLwM2MTransportConfigServer().getServerPortNoSec(), - this.context.getLwM2MTransportConfigServer().getServerHostSecurity() + ":" + this.context.getLwM2MTransportConfigServer().getServerPortSecurity()); + LwM2MTransportServerConfig lwM2MTransportServerConfig = this.context.getLwM2MTransportServerConfig(); + log.info("Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]", mode, + lwM2MTransportServerConfig.getHost(), + lwM2MTransportServerConfig.getPort(), + lwM2MTransportServerConfig.getSecureHost(), + lwM2MTransportServerConfig.getSecurePort()); } private boolean setServerRPK(LeshanServerBuilder builder) { @@ -233,39 +231,26 @@ public class LwM2mTransportServerConfiguration { return false; } - - /** - * From yml: server - * public_x: "${LWM2M_SERVER_PUBLIC_X:405354ea8893471d9296afbc8b020a5c6201b0bb25812a53b849d4480fa5f069}" - * public_y: "${LWM2M_SERVER_PUBLIC_Y:30c9237e946a3a1692c1cafaa01a238a077f632c99371348337512363f28212b}" - * private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:274671fe40ce937b8a6352cf0a418e8a39e4bf0bb9bf74c910db953c20c73802}" - */ private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { /** Get Elliptic Curve Parameter spec for secp256r1 */ AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); algoParameters.init(new ECGenParameterSpec("secp256r1")); ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (this.context.getLwM2MTransportConfigServer().getServerPublicX() != null && - !this.context.getLwM2MTransportConfigServer().getServerPublicX().isEmpty() && - this.context.getLwM2MTransportConfigServer().getServerPublicY() != null && - !this.context.getLwM2MTransportConfigServer().getServerPublicY().isEmpty()) { - /** Get point values */ - byte[] publicX = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPublicX().toCharArray()); - byte[] publicY = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPublicY().toCharArray()); - /** Create key specs */ + LwM2MTransportServerConfig serverConfig = this.context.getLwM2MTransportServerConfig(); + if (StringUtils.isNotEmpty(serverConfig.getPublicX()) && StringUtils.isNotEmpty(serverConfig.getPublicY())) { + byte[] publicX = Hex.decodeHex(serverConfig.getPublicX().toCharArray()); + byte[] publicY = Hex.decodeHex(serverConfig.getPublicY().toCharArray()); KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), parameterSpec); - /** Get keys */ this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); } - if (this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded() != null && - !this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded().isEmpty()) { - /** Get private key */ - byte[] privateS = Hex.decodeHex(this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded().toCharArray()); + String privateEncodedKey = serverConfig.getPrivateEncoded(); + if (StringUtils.isNotEmpty(privateEncodedKey)) { + byte[] privateS = Hex.decodeHex(privateEncodedKey.toCharArray()); try { this.privateKey = KeyFactory.getInstance("EC").generatePrivate(new PKCS8EncodedKeySpec(privateS)); } catch (InvalidKeySpecException ignore2) { - log.error("Invalid Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", this.context.getLwM2MTransportConfigServer().getServerPrivateEncoded()); + log.error("Invalid Server rpk.PrivateKey.getEncoded () [{}}]. PrivateKey has no EC algorithm", privateEncodedKey); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java index 4fad90910a..4e9b8a97fc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java @@ -41,7 +41,7 @@ public class LwM2mTransportServerInitializer { @PostConstruct public void init() { - if (this.context.getLwM2MTransportConfigServer().getEnableGenNewKeyPskRpk()) { + if (this.context.getLwM2MTransportServerConfig().getEnableGenNewKeyPskRpk()) { new LWM2MGenerationPSkRPkECC(); } this.startLhServer(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java index 350aebc4c7..8dabe625eb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java @@ -84,29 +84,29 @@ import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.CLIENT_NOT_AUTHORIZED; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.DEVICE_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.FR_PATH_RESOURCE_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LOG_LW2M_VALUE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LWM2M_STRATEGY_2; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.DISCOVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.EXECUTE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_CANCEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.OBSERVE_READ_ALL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.READ; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_ATTRIBUTES; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_REPLACE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper.WRITE_UPDATE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.SERVICE_CHANNEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertJsonArrayToSet; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getAckCallback; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.CLIENT_NOT_AUTHORIZED; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEVICE_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_STRATEGY_2; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.DISCOVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.EXECUTE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_READ_ALL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.READ; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_REPLACE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_UPDATE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVICE_CHANNEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertJsonArrayToSet; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getAckCallback; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.validateObjectVerFromKey; @Slf4j @Service @@ -143,12 +143,12 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @PostConstruct public void init() { - this.lwM2mTransportContextServer.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) lwM2mTransportContextServer.getLwM2MTransportConfigServer().getSessionReportTimeout()), lwM2mTransportContextServer.getLwM2MTransportConfigServer().getSessionReportTimeout(), TimeUnit.MILLISECONDS); - this.executorRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getRegisteredPoolSize(), + this.lwM2mTransportContextServer.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) lwM2mTransportContextServer.getLwM2MTransportServerConfig().getSessionReportTimeout()), lwM2mTransportContextServer.getLwM2MTransportServerConfig().getSessionReportTimeout(), TimeUnit.MILLISECONDS); + this.executorRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getRegisteredPoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel registered", SERVICE_CHANNEL))); - this.executorUpdateRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getUpdateRegisteredPoolSize(), + this.executorUpdateRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getUpdateRegisteredPoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel update registered", SERVICE_CHANNEL))); - this.executorUnRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getUnRegisteredPoolSize(), + this.executorUnRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getUnRegisteredPoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel un registered", SERVICE_CHANNEL))); this.converter = LwM2mValueConverterImpl.getInstance(); } @@ -281,7 +281,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { Set observations = leshanServer.getObservationService().getObservations(registration); observations.forEach(observation -> lwM2mTransportRequest.sendAllRequest(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), OBSERVE_CANCEL, - null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); + null, null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null)); } } @@ -339,7 +339,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { this.getInfoFirmwareUpdate(lwM2MClient); } if (pathIdVer != null) { - ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() .getModelProvider()); if (resourceModel != null && resourceModel.operations.isWritable()) { this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer); @@ -404,7 +404,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void onResourceUpdate(Optional resourceUpdateMsgOpt) { String idVer = resourceUpdateMsgOpt.get().getResourceKey(); - lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); + lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getModelProvider())); } /** @@ -413,7 +413,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void onResourceDelete(Optional resourceDeleteMsgOpt) { String pathIdVer = resourceDeleteMsgOpt.get().getResourceKey(); - lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider())); + lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getModelProvider())); } @Override @@ -429,7 +429,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } else { lwM2mTransportRequest.sendAllRequest(registration, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), lwm2mClientRpcRequest.getContentFormatName(), lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), - this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), lwm2mClientRpcRequest); + this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), lwm2mClientRpcRequest); } } catch (Exception e) { if (lwm2mClientRpcRequest == null) { @@ -457,7 +457,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { lwm2mClientRpcRequest.setRequestId(toDeviceRequest.getRequestId()); lwm2mClientRpcRequest.setSessionInfo(sessionInfo); lwm2mClientRpcRequest.setValidTypeOper(toDeviceRequest.getMethodName()); - JsonObject rpcRequest = LwM2mTransportHandler.validateJson(toDeviceRequest.getParams()); + JsonObject rpcRequest = LwM2mTransportHandlerUtil.validateJson(toDeviceRequest.getParams()); if (rpcRequest != null) { if (rpcRequest.has(lwm2mClientRpcRequest.keyNameKey)) { String targetIdVer = this.getPresentPathIntoProfile(sessionInfo, @@ -546,7 +546,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void doTrigger(Registration registration, String path) { lwM2mTransportRequest.sendAllRequest(registration, path, EXECUTE, - ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); } /** @@ -636,11 +636,11 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); Set clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); if (clientObjects != null && clientObjects.size() > 0) { - if (LWM2M_STRATEGY_2 == LwM2mTransportHandler.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { + if (LWM2M_STRATEGY_2 == LwM2mTransportHandlerUtil.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { // #2 lwM2MClient.getPendingReadRequests().addAll(clientObjects); clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(), - null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); + null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null)); } // #1 this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, READ, clientObjects); @@ -689,7 +689,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { */ private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); - if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() .getModelProvider())) { if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && lwM2MClient.getFrUpdate().getCurrentFwVersion() != null @@ -780,7 +780,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { ConcurrentHashMap finalParams = params; pathSend.forEach(target -> { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), - finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); }); if (OBSERVE.equals(typeOper)) { lwM2MClient.initReadValue(this, null); @@ -1094,10 +1094,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (pathIds.isResource()) { if (READ.equals(typeOper)) { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, - ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); } else if (OBSERVE.equals(typeOper)) { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, - null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + null, null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); } } }); @@ -1153,7 +1153,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (!pathSend.isEmpty()) { ConcurrentHashMap finalParams = lwm2mAttributesNew; pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), - finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null)); + finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null)); } }); } @@ -1170,7 +1170,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { params.clear(); params.put(OBJECT_VERSION, ""); lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), - params, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + params, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); }); } }); @@ -1183,7 +1183,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { paramAnallyzer.forEach(pathIdVer -> { if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) { lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, OBSERVE_CANCEL, null, - null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); } } ); @@ -1193,7 +1193,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, WRITE_REPLACE, ContentFormat.TLV.getName(), valueNew, - this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); } else { log.error("Failed update resource [{}] [{}]", path, valueNew); String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", @@ -1406,7 +1406,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { public void readRequestToClientFirmwareVer(Registration registration) { String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration); lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, READ, ContentFormat.TLV.getName(), - null, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + null, lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); } /** @@ -1422,7 +1422,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(objectId); String targetIdVer = LWM2M_SEPARATOR_PATH + objectId + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0; lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(), - firmwareChunk, lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout(), null); + firmwareChunk, lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion()); } } @@ -1444,7 +1444,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { - ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer() + ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() .getModelProvider()); Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId(); String objectVer = validateObjectVerFromKey(pathIdVer); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 0617847246..b79f6b5241 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -42,9 +42,9 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.TRANSPORT_DEFAULT_LWM2M_VERSION; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromIdVerToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.getVerFromPathIdVerOrId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.TRANSPORT_DEFAULT_LWM2M_VERSION; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getVerFromPathIdVerOrId; @Slf4j @Data diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index fe17ac3819..44df451be7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -25,7 +25,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil; import java.util.Arrays; import java.util.Map; @@ -34,7 +34,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer; @Service @TbLwM2mTransportComponent @@ -118,7 +118,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { */ @Override public LwM2mClient addLwM2mClientToSession(String identity) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandler.LwM2mTypeServer.CLIENT); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandlerUtil.LwM2mTypeServer.CLIENT); if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; LwM2mClient client; @@ -165,7 +165,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { - LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandler.getLwM2MClientProfileFromThingsboard(deviceProfile); + LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandlerUtil.getLwM2MClientProfileFromThingsboard(deviceProfile); if (lwM2MClientProfile != null) { profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); return true; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java index c71f0e61b0..d2c6ada458 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java @@ -21,11 +21,11 @@ import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.LwM2mTypeOper; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper; import java.util.concurrent.ConcurrentHashMap; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validPathIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.validPathIdVer; @Data public class Lwm2mClientRpcRequest { diff --git a/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh b/common/transport/lwm2m/src/main/resources/credentials/shell/lwM2M_credentials.sh old mode 100755 new mode 100644 diff --git a/common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks b/common/transport/lwm2m/src/main/resources/lwm2mserver.jks similarity index 100% rename from common/transport/lwm2m/src/main/resources/credentials/serverKeyStore.jks rename to common/transport/lwm2m/src/main/resources/lwm2mserver.jks diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java deleted file mode 100644 index cb2af9eb3f..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/lwm2m/LwM2MTransportConfigServer.java +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright © 2016-2021 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.transport.lwm2m; - -import lombok.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.server.model.LwM2mModelProvider; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; - -@Slf4j -@Component -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith' || '${service.type:null}'=='tb-core'") -public class LwM2MTransportConfigServer { - - @Getter - private String KEY_STORE_DEFAULT_RESOURCE_PATH = "credentials"; - - @Getter - private String KEY_STORE_DEFAULT_FILE = "serverKeyStore.jks"; - - @Getter - private String APP_DIR = "common"; - - @Getter - private String TRANSPORT_DIR = "transport"; - - @Getter - private String LWM2M_DIR = "lwm2m"; - - @Getter - private String SRC_DIR = "src"; - - @Getter - private String MAIN_DIR = "main"; - - @Getter - private String RESOURCES_DIR = "resources"; - - @Getter - private String BASE_DIR_PATH = System.getProperty("user.dir"); - - @Getter - // private String PATH_DATA_MICROSERVICE = "/usr/share/tb-lwm2m-transport/data$"; - private String PATH_DATA = "data"; - - @Getter - @Setter - private LwM2mModelProvider modelProvider; - - @Getter - @Value("${transport.lwm2m.timeout:}") - private Long timeout; - - @Getter - @Value("${transport.sessions.report_timeout}") - private long sessionReportTimeout; - - @Getter - @Value("${transport.lwm2m.recommended_ciphers:}") - private boolean recommendedCiphers; - - @Getter - @Value("${transport.lwm2m.recommended_supported_groups:}") - private boolean recommendedSupportedGroups; - - @Getter - @Value("${transport.lwm2m.response_pool_size:}") - private int responsePoolSize; - - @Getter - @Value("${transport.lwm2m.registered_pool_size:}") - private int registeredPoolSize; - - @Getter - @Value("${transport.lwm2m.update_registered_pool_size:}") - private int updateRegisteredPoolSize; - - @Getter - @Value("${transport.lwm2m.un_registered_pool_size:}") - private int unRegisteredPoolSize; - - @Getter - @Value("${transport.lwm2m.secure.key_store_type:}") - private String keyStoreType; - - @Getter - @Value("${transport.lwm2m.secure.key_store_path_file:}") - private String keyStorePathFile; - - @Getter - @Setter - private KeyStore keyStoreValue; - - @Getter - @Value("${transport.lwm2m.secure.key_store_password:}") - private String keyStorePasswordServer; - - @Getter - @Value("${transport.lwm2m.secure.root_alias:}") - private String rootAlias; - - @Getter - @Value("${transport.lwm2m.secure.enable_gen_new_key_psk_rpk:}") - private Boolean enableGenNewKeyPskRpk; - - @Getter - @Value("${transport.lwm2m.server.id:}") - private Integer serverId; - - @Getter - @Value("${transport.lwm2m.server.bind_address:}") - private String serverHost; - - @Getter - @Value("${transport.lwm2m.server.secure.bind_address_security:}") - private String serverHostSecurity; - - @Getter - @Value("${transport.lwm2m.server.bind_port_no_sec:}") - private Integer serverPortNoSec; - - @Getter - @Value("${transport.lwm2m.server.secure.bind_port_security:}") - private Integer serverPortSecurity; - - @Getter - @Value("${transport.lwm2m.server.secure.public_x:}") - private String serverPublicX; - - @Getter - @Value("${transport.lwm2m.server.secure.public_y:}") - private String serverPublicY; - - @Getter - @Value("${transport.lwm2m.server.secure.private_encoded:}") - private String serverPrivateEncoded; - - @Getter - @Value("${transport.lwm2m.server.secure.alias:}") - private String serverAlias; - - @PostConstruct - public void init() { - this.getInKeyStore(); - } - - private KeyStore getInKeyStore() { - try { - if (keyStoreValue != null && keyStoreValue.size() > 0) - return keyStoreValue; - } catch (KeyStoreException e) { - log.error("Uninitialized keystore [{}]", keyStoreValue.toString()); - } - Path keyStorePath = (keyStorePathFile != null && !keyStorePathFile.isEmpty()) ? Paths.get(keyStorePathFile) : - (new File(Paths.get(getBaseDirPath(), PATH_DATA, KEY_STORE_DEFAULT_RESOURCE_PATH, KEY_STORE_DEFAULT_FILE).toUri()).isFile()) ? - Paths.get(getBaseDirPath(), PATH_DATA, KEY_STORE_DEFAULT_RESOURCE_PATH, KEY_STORE_DEFAULT_FILE) : - Paths.get(getBaseDirPath(), APP_DIR, TRANSPORT_DIR, LWM2M_DIR, SRC_DIR, MAIN_DIR, RESOURCES_DIR, KEY_STORE_DEFAULT_RESOURCE_PATH, KEY_STORE_DEFAULT_FILE); - File keyStoreFile = new File(keyStorePath.toUri()); - if (keyStoreFile.isFile()) { - try { - InputStream inKeyStore = new FileInputStream(keyStoreFile); - keyStoreValue = KeyStore.getInstance(keyStoreType); - keyStoreValue.load(inKeyStore, keyStorePasswordServer == null ? null : keyStorePasswordServer.toCharArray()); - } catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException e) { - log.error("[{}] Unable to load KeyStore files server, folder is not a directory", e.getMessage()); - keyStoreValue = null; - } - log.info("[{}] Load KeyStore files server, folder is a directory", keyStoreFile.getAbsoluteFile()); - } else { - log.error("[{}] Unable to load KeyStore files server, is not a file", keyStoreFile.getAbsoluteFile()); - keyStoreValue = null; - } - return keyStoreValue; - } - - private String getBaseDirPath() { - Path FULL_FILE_PATH; - if (BASE_DIR_PATH.endsWith("bin")) { - FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("bin$", "")); - } else if (BASE_DIR_PATH.endsWith("conf")) { - FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.replaceAll("conf$", "")); - } else if (BASE_DIR_PATH.endsWith("application")) { - FULL_FILE_PATH = Paths.get(BASE_DIR_PATH.substring(0, BASE_DIR_PATH.length() - "application".length())); - } else { - FULL_FILE_PATH = Paths.get(BASE_DIR_PATH); - } - return FULL_FILE_PATH.toUri().getPath(); - } -} diff --git a/transport/lwm2m/src/main/data/credentials/serverKeyStore.jks b/transport/lwm2m/src/main/data/lwm2mserver.jks similarity index 100% rename from transport/lwm2m/src/main/data/credentials/serverKeyStore.jks rename to transport/lwm2m/src/main/data/lwm2mserver.jks From 8b3e34f0ef9d853b18eed5136d145f638c3a290d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 4 May 2021 10:33:54 +0300 Subject: [PATCH 118/127] Refactoring of LwM2M transport --- ...TransportBootstrapServerConfiguration.java | 21 ++-- .../secure/LwM2MBootstrapSecurityStore.java | 15 ++- ...LwM2mCredentialsSecurityInfoValidator.java | 17 +-- ...a => DefaultLwM2MTransportMsgHandler.java} | 105 +++++++++--------- ...java => DefaultLwM2mTransportService.java} | 77 +++++++++---- .../lwm2m/server/LwM2MTransportService.java | 22 ++++ .../lwm2m/server/LwM2mServerListener.java | 4 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 4 +- .../lwm2m/server/LwM2mTransportContext.java | 32 ++++++ ...ice.java => LwM2mTransportMsgHandler.java} | 6 +- .../lwm2m/server/LwM2mTransportRequest.java | 37 +++--- ...r.java => LwM2mTransportServerHelper.java} | 83 ++++++-------- .../LwM2mTransportServerInitializer.java | 31 ------ .../server/LwM2mVersionedModelProvider.java | 16 +-- .../lwm2m/server/client/LwM2mClient.java | 4 +- .../common/transport/TransportContext.java | 4 +- 16 files changed, 251 insertions(+), 227 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{LwM2mTransportServiceImpl.java => DefaultLwM2MTransportMsgHandler.java} (93%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{LwM2mTransportServerConfiguration.java => DefaultLwM2mTransportService.java} (83%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{LwM2mTransportService.java => LwM2mTransportMsgHandler.java} (94%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{LwM2mTransportContextServer.java => LwM2mTransportServerHelper.java} (74%) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java index 2c0e01d169..052a4f80e1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java @@ -30,7 +30,8 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSec import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import java.math.BigInteger; import java.security.AlgorithmParameters; @@ -68,10 +69,10 @@ public class LwM2MTransportBootstrapServerConfiguration { private boolean pskMode = false; @Autowired - private LwM2MTransportContextBootstrap contextBs; + private LwM2MTransportServerConfig serverConfig; @Autowired - private LwM2mTransportContextServer contextS; + private LwM2MTransportContextBootstrap contextBs; @Autowired private LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; @@ -108,8 +109,8 @@ public class LwM2MTransportBootstrapServerConfiguration { /** Create and Set DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); - dtlsConfig.setRecommendedSupportedGroupsOnly(this.contextS.getLwM2MTransportServerConfig().isRecommendedSupportedGroups()); - dtlsConfig.setRecommendedCipherSuitesOnly(this.contextS.getLwM2MTransportServerConfig().isRecommendedCiphers()); + dtlsConfig.setRecommendedSupportedGroupsOnly(serverConfig.isRecommendedSupportedGroups()); + dtlsConfig.setRecommendedCipherSuitesOnly(serverConfig.isRecommendedCiphers()); if (this.pskMode) { dtlsConfig.setSupportedCipherSuites( TLS_PSK_WITH_AES_128_CCM_8, @@ -134,10 +135,10 @@ public class LwM2MTransportBootstrapServerConfiguration { private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) { try { - if (this.contextS.getLwM2MTransportServerConfig().getKeyStoreValue() != null) { - KeyStore keyStoreServer = this.contextS.getLwM2MTransportServerConfig().getKeyStoreValue(); + if (serverConfig.getKeyStoreValue() != null) { + KeyStore keyStoreServer = serverConfig.getKeyStoreValue(); if (this.setBuilderX509(builder)) { - X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(this.contextS.getLwM2MTransportServerConfig().getRootCertificateAlias()); + X509Certificate rootCAX509Cert = (X509Certificate) keyStoreServer.getCertificate(serverConfig.getRootCertificateAlias()); if (rootCAX509Cert != null) { X509Certificate[] trustedCertificates = new X509Certificate[1]; trustedCertificates[0] = rootCAX509Cert; @@ -168,8 +169,8 @@ public class LwM2MTransportBootstrapServerConfiguration { * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks */ try { - X509Certificate serverCertificate = (X509Certificate) this.contextS.getLwM2MTransportServerConfig().getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getCertificateAlias()); - PrivateKey privateKey = (PrivateKey) this.contextS.getLwM2MTransportServerConfig().getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getCertificateAlias(), this.contextS.getLwM2MTransportServerConfig().getKeyStorePassword() == null ? null : this.contextS.getLwM2MTransportServerConfig().getKeyStorePassword().toCharArray()); + X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getCertificateAlias()); + PrivateKey privateKey = (PrivateKey) serverConfig.getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getCertificateAlias(), serverConfig.getKeyStorePassword() == null ? null : serverConfig.getKeyStorePassword().toCharArray()); PublicKey publicKey = serverCertificate.getPublicKey(); if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) { builder.setPublicKey(serverCertificate.getPublicKey()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 7aefe19da5..9376236af5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -34,7 +34,8 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil; import java.io.IOException; @@ -59,12 +60,14 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private final LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator; - private final LwM2mTransportContextServer context; + private final LwM2mTransportContext context; + private final LwM2mTransportServerHelper helper; - public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContextServer context) { + public LwM2MBootstrapSecurityStore(EditableBootstrapConfigStore bootstrapConfigStore, LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, LwM2mTransportContext context, LwM2mTransportServerHelper helper) { this.bootstrapConfigStore = bootstrapConfigStore; this.lwM2MCredentialsSecurityInfoValidator = lwM2MCredentialsSecurityInfoValidator; this.context = context; + this.helper = helper; } @Override @@ -158,19 +161,19 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); UUID sessionUUiD = UUID.randomUUID(); - TransportProtos.SessionInfoProto sessionInfo = context.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); + TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, sessionInfo)); if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndPoint()); - context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo); + helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo); return lwM2MBootstrapConfig; } else { log.error(" [{}] Different values SecurityMode between of client and profile.", store.getEndPoint()); log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LW2M_ERROR, store.getEndPoint()); - context.sendParametersOnThingsboardTelemetry(context.getKvLogyToThingsboard(logMsg), sessionInfo); + helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo); return null; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index e9a09c54d3..cbba4eb490 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.secure; import com.google.gson.JsonObject; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.SecurityUtil; @@ -26,7 +27,9 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContextServer; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil; import java.io.IOException; @@ -44,13 +47,11 @@ import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X5 @Slf4j @Component @TbLwM2mTransportComponent +@RequiredArgsConstructor public class LwM2mCredentialsSecurityInfoValidator { - private final LwM2mTransportContextServer contextS; - - public LwM2mCredentialsSecurityInfoValidator(LwM2mTransportContextServer contextS) { - this.contextS = contextS; - } + private final LwM2mTransportContext context; + private final LwM2MTransportServerConfig config; /** * Request to thingsboard Response from thingsboard ValidateDeviceLwM2MCredentials @@ -61,7 +62,7 @@ public class LwM2mCredentialsSecurityInfoValidator { public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportHandlerUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; - contextS.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), + context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { @@ -81,7 +82,7 @@ public class LwM2mCredentialsSecurityInfoValidator { } }); try { - latch.await(contextS.getLwM2MTransportServerConfig().getTimeout(), TimeUnit.MILLISECONDS); + latch.await(config.getTimeout(), TimeUnit.MILLISECONDS); } catch (InterruptedException e) { log.error("Failed to await credentials!", e); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java similarity index 93% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index 8dabe625eb..5292c1f9fb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServiceImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -54,6 +54,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotif import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; @@ -111,44 +112,43 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle @Slf4j @Service @TbLwM2mTransportComponent -public class LwM2mTransportServiceImpl implements LwM2mTransportService { +public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler { private ExecutorService executorRegistered; private ExecutorService executorUpdateRegistered; private ExecutorService executorUnRegistered; private LwM2mValueConverterImpl converter; - private FirmwareDataCache firmwareDataCache; - private final TransportService transportService; - - public final LwM2mTransportContextServer lwM2mTransportContextServer; - + private final LwM2mTransportContext context; + private final LwM2MTransportServerConfig config; + private final FirmwareDataCache firmwareDataCache; + private final LwM2mTransportServerHelper helper; private final LwM2mClientContext lwM2mClientContext; - - private final LeshanServer leshanServer; - private final LwM2mTransportRequest lwM2mTransportRequest; - public LwM2mTransportServiceImpl(TransportService transportService, LwM2mTransportContextServer lwM2mTransportContextServer, - LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, - @Lazy LwM2mTransportRequest lwM2mTransportRequest, FirmwareDataCache firmwareDataCache) { + public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, + LwM2mClientContext lwM2mClientContext, + @Lazy LwM2mTransportRequest lwM2mTransportRequest, + FirmwareDataCache firmwareDataCache, + LwM2mTransportContext context) { this.transportService = transportService; - this.lwM2mTransportContextServer = lwM2mTransportContextServer; + this.config = config; + this.helper = helper; this.lwM2mClientContext = lwM2mClientContext; - this.leshanServer = leshanServer; this.lwM2mTransportRequest = lwM2mTransportRequest; this.firmwareDataCache = firmwareDataCache; + this.context = context; } @PostConstruct public void init() { - this.lwM2mTransportContextServer.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) lwM2mTransportContextServer.getLwM2MTransportServerConfig().getSessionReportTimeout()), lwM2mTransportContextServer.getLwM2MTransportServerConfig().getSessionReportTimeout(), TimeUnit.MILLISECONDS); - this.executorRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getRegisteredPoolSize(), + this.context.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS); + this.executorRegistered = Executors.newFixedThreadPool(this.config.getRegisteredPoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel registered", SERVICE_CHANNEL))); - this.executorUpdateRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getUpdateRegisteredPoolSize(), + this.executorUpdateRegistered = Executors.newFixedThreadPool(this.config.getUpdateRegisteredPoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel update registered", SERVICE_CHANNEL))); - this.executorUnRegistered = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getUnRegisteredPoolSize(), + this.executorUnRegistered = Executors.newFixedThreadPool(this.config.getUnRegisteredPoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel un registered", SERVICE_CHANNEL))); this.converter = LwM2mValueConverterImpl.getInstance(); } @@ -278,10 +278,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void setCancelObservations(Registration registration) { if (registration != null) { - Set observations = leshanServer.getObservationService().getObservations(registration); + Set observations = context.getServer().getObservationService().getObservations(registration); observations.forEach(observation -> lwM2mTransportRequest.sendAllRequest(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), OBSERVE_CANCEL, - null, null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null)); + null, null, this.config.getTimeout(), null)); } } @@ -333,13 +333,13 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); - Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); + Object valueNew = this.helper.getValueFromKvProto(tsKvProto.getKv()); //TODO: react on change of the firmware name. if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { this.getInfoFirmwareUpdate(lwM2MClient); } if (pathIdVer != null) { - ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() + ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config .getModelProvider()); if (resourceModel != null && resourceModel.operations.isWritable()) { this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer); @@ -360,7 +360,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } else if (msg.getSharedDeletedCount() > 0) { msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); - Object valueNew = this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()); + Object valueNew = this.helper.getValueFromKvProto(tsKvProto.getKv()); if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew); } @@ -404,7 +404,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void onResourceUpdate(Optional resourceUpdateMsgOpt) { String idVer = resourceUpdateMsgOpt.get().getResourceKey(); - lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getModelProvider())); + lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.config.getModelProvider())); } /** @@ -413,7 +413,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void onResourceDelete(Optional resourceDeleteMsgOpt) { String pathIdVer = resourceDeleteMsgOpt.get().getResourceKey(); - lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getModelProvider())); + lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.config.getModelProvider())); } @Override @@ -429,7 +429,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } else { lwM2mTransportRequest.sendAllRequest(registration, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), lwm2mClientRpcRequest.getContentFormatName(), lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), - this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), lwm2mClientRpcRequest); + this.config.getTimeout(), lwm2mClientRpcRequest); } } catch (Exception e) { if (lwm2mClientRpcRequest == null) { @@ -546,7 +546,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { @Override public void doTrigger(Registration registration, String path) { lwM2mTransportRequest.sendAllRequest(registration, path, EXECUTE, - ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + ContentFormat.TLV.getName(), null, this.config.getTimeout(), null); } /** @@ -579,7 +579,8 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * * @param registration - */ - protected void onAwakeDev(Registration registration) { + @Override + public void onAwakeDev(Registration registration) { log.info("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client is awake!", registration.getId()); //TODO: associate endpointId with device information. @@ -610,13 +611,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { * @param logMsg - text msg * @param registrationId - Id of Registration LwM2M Client */ + @Override public void sendLogsToThingsboard(String logMsg, String registrationId) { SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId); if (logMsg != null && sessionInfo != null) { if(logMsg.length() > 1024){ logMsg = logMsg.substring(0, 1024); } - this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(this.lwM2mTransportContextServer.getKvLogyToThingsboard(logMsg), sessionInfo); + this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvLogyToThingsboard(logMsg), sessionInfo); } } @@ -640,7 +642,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { // #2 lwM2MClient.getPendingReadRequests().addAll(clientObjects); clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(), - null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null)); + null, this.config.getTimeout(), null)); } // #1 this.initReadAttrTelemetryObserveToClient(registration, lwM2MClient, READ, clientObjects); @@ -689,7 +691,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { */ private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); - if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() + if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config .getModelProvider())) { if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && lwM2MClient.getFrUpdate().getCurrentFwVersion() != null @@ -728,10 +730,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); if (results != null && sessionInfo != null) { if (results.getResultAttributes().size() > 0) { - this.lwM2mTransportContextServer.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo); + this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo); } if (results.getResultTelemetries().size() > 0) { - this.lwM2mTransportContextServer.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo); + this.helper.sendParametersOnThingsboardTelemetry(results.getResultTelemetries(), sessionInfo); } } } catch (Exception e) { @@ -780,7 +782,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { ConcurrentHashMap finalParams = params; pathSend.forEach(target -> { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), - finalParams != null ? finalParams.get(target) : null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + finalParams != null ? finalParams.get(target) : null, this.config.getTimeout(), null); }); if (OBSERVE.equals(typeOper)) { lwM2MClient.initReadValue(this, null); @@ -865,7 +867,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { LwM2mResource resourceValue = lwM2MClient != null ? getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) : null; if (resourceValue != null) { ResourceModel.Type currentType = resourceValue.getType(); - ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); + ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); Object valueKvProto = null; if (resourceValue.isMultiInstances()) { valueKvProto = new JsonObject(); @@ -882,7 +884,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { valueKvProto = this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); } - return valueKvProto != null ? this.lwM2mTransportContextServer.getKvAttrTelemetryToThingsboard(currentType, resourceName, valueKvProto, resourceValue.isMultiInstances()) : null; + return valueKvProto != null ? this.helper.getKvAttrTelemetryToThingsboard(currentType, resourceName, valueKvProto, resourceValue.isMultiInstances()) : null; } } catch (Exception e) { log.error("Failed to add parameters.", e); @@ -901,7 +903,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); ResourceModel.Type currentType = resourceValue.getType(); - ResourceModel.Type expectedType = this.lwM2mTransportContextServer.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); + ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); } @@ -1094,10 +1096,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (pathIds.isResource()) { if (READ.equals(typeOper)) { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, - ContentFormat.TLV.getName(), null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + ContentFormat.TLV.getName(), null, this.config.getTimeout(), null); } else if (OBSERVE.equals(typeOper)) { lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, - null, null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + null, null, this.config.getTimeout(), null); } } }); @@ -1153,7 +1155,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (!pathSend.isEmpty()) { ConcurrentHashMap finalParams = lwm2mAttributesNew; pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), - finalParams.get(target), this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null)); + finalParams.get(target), this.config.getTimeout(), null)); } }); } @@ -1170,7 +1172,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { params.clear(); params.put(OBJECT_VERSION, ""); lwM2mTransportRequest.sendAllRequest(registration, target, WRITE_ATTRIBUTES, ContentFormat.TLV.getName(), - params, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + params, this.config.getTimeout(), null); }); } }); @@ -1183,7 +1185,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { paramAnallyzer.forEach(pathIdVer -> { if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) { lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, OBSERVE_CANCEL, null, - null, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + null, this.config.getTimeout(), null); } } ); @@ -1193,7 +1195,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), path, WRITE_REPLACE, ContentFormat.TLV.getName(), valueNew, - this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + this.config.getTimeout(), null); } else { log.error("Failed update resource [{}] [{}]", path, valueNew); String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", @@ -1271,7 +1273,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { // #2.1 lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> { this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), - this.lwM2mTransportContextServer.getValueFromKvProto(tsKvProto.getKv()), pathIdVer); + this.helper.getValueFromKvProto(tsKvProto.getKv()), pathIdVer); }); } @@ -1288,7 +1290,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { return null; } else { return SessionInfoProto.newBuilder() - .setNodeId(this.lwM2mTransportContextServer.getNodeId()) + .setNodeId(this.context.getNodeId()) .setSessionIdMSB(lwM2MClient.getSessionId().getMostSignificantBits()) .setSessionIdLSB(lwM2MClient.getSessionId().getLeastSignificantBits()) .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) @@ -1358,7 +1360,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (keyNamesMap.values().size() > 0) { try { //#1.2 - TransportProtos.GetAttributeRequestMsg getAttributeMsg = lwM2mTransportContextServer.getAdaptor().convertToGetAttributes(null, keyNamesMap.values()); + TransportProtos.GetAttributeRequestMsg getAttributeMsg = helper.getAdaptor().convertToGetAttributes(null, keyNamesMap.values()); transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); } catch (AdaptorException e) { log.warn("Failed to decode get attributes request", e); @@ -1406,7 +1408,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { public void readRequestToClientFirmwareVer(Registration registration) { String pathIdVer = convertPathFromObjectIdToIdVer(FR_PATH_RESOURCE_VER_ID, registration); lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, READ, ContentFormat.TLV.getName(), - null, lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + null, config.getTimeout(), null); } /** @@ -1422,7 +1424,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(objectId); String targetIdVer = LWM2M_SEPARATOR_PATH + objectId + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0; lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(), - firmwareChunk, lwM2mTransportContextServer.getLwM2MTransportServerConfig().getTimeout(), null); + firmwareChunk, config.getTimeout(), null); log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion()); } } @@ -1444,7 +1446,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { } private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { - ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() + ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config .getModelProvider()); Integer objectId = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId(); String objectVer = validateObjectVerFromKey(pathIdVer); @@ -1453,9 +1455,4 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); } - @Override - public String getName() { - return "LWM2M"; - } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java similarity index 83% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index e320e3fcfa..418e3d003b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.californium.core.network.stack.BlockwiseLayer; @@ -29,14 +30,16 @@ import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.security.DefaultAuthorizer; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.eclipse.leshan.server.security.SecurityChecker; -import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.KeyFactory; @@ -66,32 +69,53 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g @Slf4j @Component @TbLwM2mTransportComponent -public class LwM2mTransportServerConfiguration { +@RequiredArgsConstructor +public class DefaultLwM2mTransportService implements LwM2MTransportService { + private PublicKey publicKey; private PrivateKey privateKey; private boolean pskMode = false; - private final LwM2mTransportContextServer context; + + private final LwM2mTransportContext context; + private final LwM2MTransportServerConfig config; + private final LwM2mTransportServerHelper helper; + private final LwM2mTransportMsgHandler handler; private final CaliforniumRegistrationStore registrationStore; private final EditableSecurityStore securityStore; private final LwM2mClientContext lwM2mClientContext; - public LwM2mTransportServerConfiguration(LwM2mTransportContextServer context, CaliforniumRegistrationStore registrationStore, EditableSecurityStore securityStore, LwM2mClientContext lwM2mClientContext) { - this.context = context; - this.registrationStore = registrationStore; - this.securityStore = securityStore; - this.lwM2mClientContext = lwM2mClientContext; + private LeshanServer server; + + @PostConstruct + public void init() { + if (config.getEnableGenNewKeyPskRpk()) { + new LWM2MGenerationPSkRPkECC(); + } + this.server = getLhServer(config.getPort(), config.getSecurePort()); + this.startLhServer(); + this.context.setServer(server); + } + + private void startLhServer() { + log.info("Starting LwM2M transport Server..."); + this.server.start(); + LwM2mServerListener lhServerCertListener = new LwM2mServerListener(handler); + this.server.getRegistrationService().addListener(lhServerCertListener.registrationListener); + this.server.getPresenceService().addListener(lhServerCertListener.presenceListener); + this.server.getObservationService().addListener(lhServerCertListener.observationListener); } - @Bean - public LeshanServer getLeshanServer() { - log.info("Starting LwM2M transport Server... PostConstruct"); - return this.getLhServer(this.context.getLwM2MTransportServerConfig().getPort(), this.context.getLwM2MTransportServerConfig().getSecurePort()); + @PreDestroy + public void shutdown() { + log.info("Stopping LwM2M transport Server!"); + server.destroy(); + log.info("LwM2M transport Server stopped!"); } private LeshanServer getLhServer(Integer serverPortNoSec, Integer serverSecurePort) { LeshanServerBuilder builder = new LeshanServerBuilder(); - builder.setLocalAddress(this.context.getLwM2MTransportServerConfig().getHost(), serverPortNoSec); - builder.setLocalSecureAddress(this.context.getLwM2MTransportServerConfig().getSecureHost(), serverSecurePort); + builder.setLocalAddress(config.getHost(), serverPortNoSec); + builder.setLocalSecureAddress(config.getSecureHost(), serverSecurePort); builder.setDecoder(new DefaultLwM2mNodeDecoder()); /** Use a magic converter to support bad type send by the UI. */ builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); @@ -103,8 +127,8 @@ public class LwM2mTransportServerConfiguration { builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort)); /** Define model provider (Create Models )*/ - LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.context); - this.context.getLwM2MTransportServerConfig().setModelProvider(modelProvider); + LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.helper, this.context); + config.setModelProvider(modelProvider); builder.setObjectModelProvider(modelProvider); /** Create credentials */ @@ -118,8 +142,8 @@ public class LwM2mTransportServerConfiguration { /** Create DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setServerOnly(true); - dtlsConfig.setRecommendedSupportedGroupsOnly(this.context.getLwM2MTransportServerConfig().isRecommendedSupportedGroups()); - dtlsConfig.setRecommendedCipherSuitesOnly(this.context.getLwM2MTransportServerConfig().isRecommendedCiphers()); + dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups()); + dtlsConfig.setRecommendedCipherSuitesOnly(config.isRecommendedCiphers()); if (this.pskMode) { dtlsConfig.setSupportedCipherSuites( TLS_PSK_WITH_AES_128_CCM_8, @@ -141,9 +165,9 @@ public class LwM2mTransportServerConfiguration { private void setServerWithCredentials(LeshanServerBuilder builder) { try { - if (this.context.getLwM2MTransportServerConfig().getKeyStoreValue() != null) { + if (config.getKeyStoreValue() != null) { if (this.setBuilderX509(builder)) { - X509Certificate rootCAX509Cert = (X509Certificate) this.context.getLwM2MTransportServerConfig().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportServerConfig().getRootCertificateAlias()); + X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias()); if (rootCAX509Cert != null) { X509Certificate[] trustedCertificates = new X509Certificate[1]; trustedCertificates[0] = rootCAX509Cert; @@ -178,8 +202,8 @@ public class LwM2mTransportServerConfiguration { private boolean setBuilderX509(LeshanServerBuilder builder) { try { - X509Certificate serverCertificate = (X509Certificate) this.context.getLwM2MTransportServerConfig().getKeyStoreValue().getCertificate(this.context.getLwM2MTransportServerConfig().getCertificateAlias()); - PrivateKey privateKey = (PrivateKey) this.context.getLwM2MTransportServerConfig().getKeyStoreValue().getKey(this.context.getLwM2MTransportServerConfig().getCertificateAlias(), this.context.getLwM2MTransportServerConfig().getKeyStorePassword() == null ? null : this.context.getLwM2MTransportServerConfig().getKeyStorePassword().toCharArray()); + X509Certificate serverCertificate = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getCertificateAlias()); + PrivateKey privateKey = (PrivateKey) config.getKeyStoreValue().getKey(config.getCertificateAlias(), config.getKeyStorePassword() == null ? null : config.getKeyStorePassword().toCharArray()); PublicKey publicKey = serverCertificate.getPublicKey(); if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) { builder.setPublicKey(serverCertificate.getPublicKey()); @@ -208,7 +232,7 @@ public class LwM2mTransportServerConfiguration { } private void infoPramsUri(String mode) { - LwM2MTransportServerConfig lwM2MTransportServerConfig = this.context.getLwM2MTransportServerConfig(); + LwM2MTransportServerConfig lwM2MTransportServerConfig = config; log.info("Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]", mode, lwM2MTransportServerConfig.getHost(), lwM2MTransportServerConfig.getPort(), @@ -236,7 +260,7 @@ public class LwM2mTransportServerConfiguration { AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); algoParameters.init(new ECGenParameterSpec("secp256r1")); ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - LwM2MTransportServerConfig serverConfig = this.context.getLwM2MTransportServerConfig(); + LwM2MTransportServerConfig serverConfig = config; if (StringUtils.isNotEmpty(serverConfig.getPublicX()) && StringUtils.isNotEmpty(serverConfig.getPublicY())) { byte[] publicX = Hex.decodeHex(serverConfig.getPublicX().toCharArray()); byte[] publicY = Hex.decodeHex(serverConfig.getPublicY().toCharArray()); @@ -283,4 +307,9 @@ public class LwM2mTransportServerConfiguration { params); } + @Override + public String getName() { + return "LWM2M"; + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java new file mode 100644 index 0000000000..6ad033503c --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MTransportService.java @@ -0,0 +1,22 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server; + +import org.thingsboard.server.common.data.TbTransportService; + +public interface LwM2MTransportService extends TbTransportService { + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index c8108b10f5..f4084708c3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -32,9 +32,9 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle @Slf4j public class LwM2mServerListener { - private final LwM2mTransportServiceImpl service; + private final LwM2mTransportMsgHandler service; - public LwM2mServerListener(LwM2mTransportServiceImpl service) { + public LwM2mServerListener(LwM2mTransportMsgHandler service) { this.service = service; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java index 6c0d6f29de..3a9114aa43 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java @@ -35,10 +35,10 @@ import java.util.Optional; @Slf4j public class LwM2mSessionMsgListener implements GenericFutureListener>, SessionMsgListener { - private LwM2mTransportServiceImpl service; + private DefaultLwM2MTransportMsgHandler service; private TransportProtos.SessionInfoProto sessionInfo; - public LwM2mSessionMsgListener(LwM2mTransportServiceImpl service, TransportProtos.SessionInfoProto sessionInfo) { + public LwM2mSessionMsgListener(DefaultLwM2MTransportMsgHandler service, TransportProtos.SessionInfoProto sessionInfo) { this.service = service; this.sessionInfo = sessionInfo; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java new file mode 100644 index 0000000000..6ae204affc --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContext.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server; + +import lombok.Getter; +import lombok.Setter; +import org.eclipse.leshan.server.californium.LeshanServer; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.transport.TransportContext; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; + +@Component +@TbLwM2mTransportComponent +public class LwM2mTransportContext extends TransportContext { + + @Getter @Setter + private LeshanServer server; + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java similarity index 94% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java index dec1e7889d..d620492173 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java @@ -27,7 +27,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcReques import java.util.Collection; import java.util.Optional; -public interface LwM2mTransportService extends TbTransportService { +public interface LwM2mTransportMsgHandler { void onRegistered(Registration registration, Collection previousObsersations); @@ -60,4 +60,8 @@ public interface LwM2mTransportService extends TbTransportService { void doTrigger(Registration registration, String path); void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); + + void onAwakeDev(Registration registration); + + void sendLogsToThingsboard(String msg, String registrationId); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index a49e9dbb23..6a1be12938 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; @@ -50,6 +51,7 @@ import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; @@ -82,35 +84,22 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle @Slf4j @Service @TbLwM2mTransportComponent +@RequiredArgsConstructor public class LwM2mTransportRequest { private ExecutorService executorResponse; public LwM2mValueConverterImpl converter; - private final LwM2mTransportContextServer lwM2mTransportContextServer; - + private final LwM2mTransportContext context; + private final LwM2MTransportServerConfig config; + private final LwM2mTransportServerHelper lwM2MTransportServerHelper; private final LwM2mClientContext lwM2mClientContext; - - private final LeshanServer leshanServer; - - private final LwM2mTransportServiceImpl serviceImpl; - - private final TransportService transportService; - - public LwM2mTransportRequest(LwM2mTransportContextServer lwM2mTransportContextServer, - LwM2mClientContext lwM2mClientContext, LeshanServer leshanServer, - LwM2mTransportServiceImpl serviceImpl, TransportService transportService) { - this.lwM2mTransportContextServer = lwM2mTransportContextServer; - this.lwM2mClientContext = lwM2mClientContext; - this.leshanServer = leshanServer; - this.serviceImpl = serviceImpl; - this.transportService = transportService; - } + private final DefaultLwM2MTransportMsgHandler serviceImpl; @PostConstruct public void init() { this.converter = LwM2mValueConverterImpl.getInstance(); - executorResponse = Executors.newFixedThreadPool(this.lwM2mTransportContextServer.getLwM2MTransportServerConfig().getResponsePoolSize(), + executorResponse = Executors.newFixedThreadPool(this.config.getResponsePoolSize(), new NamedThreadFactory(String.format("LwM2M %s channel response", RESPONSE_CHANNEL))); } @@ -158,10 +147,10 @@ public class LwM2mTransportRequest { * At server side this will not remove the observation from the observation store, to do it you need to use * {@code ObservationService#cancelObservation()} */ - leshanServer.getObservationService().cancelObservations(registration, target); + context.getServer().getObservationService().cancelObservations(registration, target); break; case EXECUTE: - resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() + resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config .getModelProvider()); if (params != null && !resourceModel.multiple) { request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModel.type, ResourceModel.Type.STRING, resultIds)); @@ -171,7 +160,7 @@ public class LwM2mTransportRequest { break; case WRITE_REPLACE: // Request to write a String Single-Instance Resource using the TLV content format. - resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportServerConfig() + resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config .getModelProvider()); if (contentFormat.equals(ContentFormat.TLV)) { request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), @@ -232,7 +221,7 @@ public class LwM2mTransportRequest { serviceImpl.sentRpcRequest(rpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); } } else if (OBSERVE_READ_ALL.name().equals(typeOper.name())) { - Set observations = leshanServer.getObservationService().getObservations(registration); + Set observations = context.getServer().getObservationService().getObservations(registration); Set observationPaths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); String msg = String.format("%s: type operation %s observation paths - %s", LOG_LW2M_INFO, OBSERVE_READ_ALL.type, observationPaths); @@ -259,7 +248,7 @@ public class LwM2mTransportRequest { @SuppressWarnings("unchecked") private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { - leshanServer.send(registration, request, timeoutInMs, (ResponseCallback) response -> { + context.getServer().send(registration, request, timeoutInMs, (ResponseCallback) response -> { if (!lwM2MClient.isInit()) { lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java similarity index 74% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index e1a322f646..210f2451a7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportContextServer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java @@ -31,6 +31,7 @@ package org.thingsboard.server.transport.lwm2m.server; */ import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.DDFFileParser; import org.eclipse.leshan.core.model.DefaultDDFFileValidator; @@ -39,11 +40,8 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.codec.CodecException; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.TransportContext; -import org.thingsboard.server.common.transport.TransportResourceCache; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; -import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; @@ -62,34 +60,16 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle @Slf4j @Component @TbLwM2mTransportComponent -public class LwM2mTransportContextServer extends TransportContext { +@RequiredArgsConstructor +public class LwM2mTransportServerHelper { - - private final LwM2MTransportServerConfig lwM2MTransportServerConfig; + private final LwM2mTransportContext context; private final TransportService transportService; - private final TransportResourceCache transportResourceCache; - - @Getter private final LwM2MJsonAdaptor adaptor; - public LwM2mTransportContextServer(LwM2MTransportServerConfig lwM2MTransportServerConfig, TransportService transportService, TransportResourceCache transportResourceCache, LwM2MJsonAdaptor adaptor) { - this.lwM2MTransportServerConfig = lwM2MTransportServerConfig; - this.transportService = transportService; - this.transportResourceCache = transportResourceCache; - this.adaptor = adaptor; - } - - public LwM2MTransportServerConfig getLwM2MTransportServerConfig() { - return this.lwM2MTransportServerConfig; - } - - public TransportResourceCache getTransportResourceCache() { - return this.transportResourceCache; - } - /** * send to Thingsboard Attribute || Telemetry * @@ -134,7 +114,7 @@ public class LwM2mTransportContextServer extends TransportContext { */ public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) { return SessionInfoProto.newBuilder() - .setNodeId(this.getNodeId()) + .setNodeId(context.getNodeId()) .setSessionIdMSB(mostSignificantBits) .setSessionIdLSB(leastSignificantBits) .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) @@ -165,8 +145,8 @@ public class LwM2mTransportContextServer extends TransportContext { * @param logMsg - info about Logs * @return- KeyValueProto for telemetry (Logs) */ - public List getKvLogyToThingsboard(String logMsg) { - List result = new ArrayList<>(); + public List getKvLogyToThingsboard(String logMsg) { + List result = new ArrayList<>(); result.add(TransportProtos.KeyValueProto.newBuilder() .setKey(LOG_LW2M_TELEMETRY) .setType(TransportProtos.KeyValueType.STRING_V) @@ -179,32 +159,31 @@ public class LwM2mTransportContextServer extends TransportContext { * @throws CodecException - */ - public TransportProtos.KeyValueProto getKvAttrTelemetryToThingsboard(ResourceModel.Type resourceType, String resourceName, Object value, boolean isMultiInstances) { - TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(resourceName); - if (isMultiInstances) { - kvProto.setType(TransportProtos.KeyValueType.JSON_V) - .setJsonV((String) value); + public TransportProtos.KeyValueProto getKvAttrTelemetryToThingsboard(ResourceModel.Type resourceType, String resourceName, Object value, boolean isMultiInstances) { + TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(resourceName); + if (isMultiInstances) { + kvProto.setType(TransportProtos.KeyValueType.JSON_V) + .setJsonV((String) value); + } else { + switch (resourceType) { + case BOOLEAN: + kvProto.setType(BOOLEAN_V).setBoolV((Boolean) value).build(); + break; + case STRING: + case TIME: + case OPAQUE: + case OBJLNK: + kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV((String) value); + break; + case INTEGER: + kvProto.setType(TransportProtos.KeyValueType.LONG_V).setLongV((Long) value); + break; + case FLOAT: + kvProto.setType(TransportProtos.KeyValueType.DOUBLE_V).setDoubleV((Double) value); } - else { - switch (resourceType) { - case BOOLEAN: - kvProto.setType(BOOLEAN_V).setBoolV((Boolean) value).build(); - break; - case STRING: - case TIME: - case OPAQUE: - case OBJLNK: - kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV((String) value); - break; - case INTEGER: - kvProto.setType(TransportProtos.KeyValueType.LONG_V).setLongV((Long) value); - break; - case FLOAT: - kvProto.setType(TransportProtos.KeyValueType.DOUBLE_V).setDoubleV((Double) value); - } - } - return kvProto.build(); } + return kvProto.build(); + } /** * @@ -230,7 +209,7 @@ public class LwM2mTransportContextServer extends TransportContext { throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType); } - public Object getValueFromKvProto (TransportProtos.KeyValueProto kv) { + public Object getValueFromKvProto(TransportProtos.KeyValueProto kv) { switch (kv.getType()) { case BOOLEAN_V: return kv.getBoolV(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java index 4e9b8a97fc..1b1ace3c5b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java @@ -30,35 +30,4 @@ import javax.annotation.PreDestroy; @TbLwM2mTransportComponent public class LwM2mTransportServerInitializer { - @Autowired - private LwM2mTransportServiceImpl service; - - @Autowired - private LeshanServer leshanServer; - - @Autowired - private LwM2mTransportContextServer context; - - @PostConstruct - public void init() { - if (this.context.getLwM2MTransportServerConfig().getEnableGenNewKeyPskRpk()) { - new LWM2MGenerationPSkRPkECC(); - } - this.startLhServer(); - } - - private void startLhServer() { - this.leshanServer.start(); - LwM2mServerListener lhServerCertListener = new LwM2mServerListener(service); - this.leshanServer.getRegistrationService().addListener(lhServerCertListener.registrationListener); - this.leshanServer.getPresenceService().addListener(lhServerCertListener.presenceListener); - this.leshanServer.getObservationService().addListener(lhServerCertListener.observationListener); - } - - @PreDestroy - public void shutdown() { - log.info("Stopping LwM2M transport Server!"); - leshanServer.destroy(); - log.info("LwM2M transport Server stopped!"); - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java index 3705547325..bb27b66d69 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.DefaultDDFFileValidator; import org.eclipse.leshan.core.model.LwM2mModel; @@ -37,6 +38,7 @@ import static org.thingsboard.server.common.data.ResourceType.LWM2M_MODEL; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; @Slf4j +@RequiredArgsConstructor public class LwM2mVersionedModelProvider implements LwM2mModelProvider { /** @@ -46,12 +48,8 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { * Value = TenantId */ private final LwM2mClientContext lwM2mClientContext; - private final LwM2mTransportContextServer lwM2mTransportContextServer; - - public LwM2mVersionedModelProvider(LwM2mClientContext lwM2mClientContext, LwM2mTransportContextServer lwM2mTransportContextServer) { - this.lwM2mClientContext = lwM2mClientContext; - this.lwM2mTransportContextServer = lwM2mTransportContextServer; - } + private final LwM2mTransportServerHelper helper; + private final LwM2mTransportContext context; private String getKeyIdVer(Integer objectId, String version) { return objectId != null ? objectId + LWM2M_SEPARATOR_KEY + ((version == null || version.isEmpty()) ? ObjectModel.DEFAULT_VERSION : version) : null; @@ -120,11 +118,9 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { private ObjectModel getObjectModelDynamic(Integer objectId, String version) { String key = getKeyIdVer(objectId, version); - Optional tbResource = lwM2mTransportContextServer - .getTransportResourceCache() - .get(this.tenantId, LWM2M_MODEL, key); + Optional tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key); - return tbResource.map(resource -> lwM2mTransportContextServer.parseFromXmlToObjectModel( + return tbResource.map(resource -> helper.parseFromXmlToObjectModel( Base64.getDecoder().decode(resource.getData()), key + ".xml", new DefaultDDFFileValidator())).orElse(null); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index b79f6b5241..df212790da 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -27,7 +27,7 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServiceImpl; +import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.Collection; @@ -172,7 +172,7 @@ public class LwM2mClient implements Cloneable { .collect(Collectors.toSet()); } - public void initReadValue(LwM2mTransportServiceImpl serviceImpl, String path) { + public void initReadValue(DefaultLwM2MTransportMsgHandler serviceImpl, String path) { if (path != null) { this.pendingReadRequests.remove(path); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index d5f779ab48..d4798108c6 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -51,11 +51,13 @@ public abstract class TransportContext { @Getter private ExecutorService executor; - @Getter @Autowired private FirmwareDataCache firmwareDataCache; + @Autowired + private TransportResourceCache transportResourceCache; + @PostConstruct public void init() { executor = ThingsBoardExecutors.newWorkStealingPool(50, getClass()); From 54543ae4b51078ed6cb8ff33b8da45d02904bd69 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 4 May 2021 11:47:38 +0300 Subject: [PATCH 119/127] Merge improvements from outdated PR 4527 --- .../src/main/resources/thingsboard.yml | 1 + .../lwm2m/client/LwM2mSoftwareUpdate.java | 27 +++++ .../config/LwM2MTransportServerConfig.java | 17 ++- .../DefaultLwM2MTransportMsgHandler.java | 6 +- .../server/DefaultLwM2mTransportService.java | 30 ++--- .../lwm2m/server/LwM2mNetworkConfig.java | 105 +++++++++--------- .../lwm2m/server/LwM2mTransportRequest.java | 47 ++++++-- 7 files changed, 146 insertions(+), 87 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/client/LwM2mSoftwareUpdate.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 83f1c8fa02..d14adc0573 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -674,6 +674,7 @@ transport: registered_pool_size: "${LWM2M_REGISTERED_POOL_SIZE:10}" update_registered_pool_size: "${LWM2M_UPDATE_REGISTERED_POOL_SIZE:10}" un_registered_pool_size: "${LWM2M_UN_REGISTERED_POOL_SIZE:10}" + log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}" # Use redis for Security and Registration stores redis.enabled: "${LWM2M_REDIS_ENABLED:false}" snmp: diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/client/LwM2mSoftwareUpdate.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/client/LwM2mSoftwareUpdate.java new file mode 100644 index 0000000000..ed8b316fed --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/client/LwM2mSoftwareUpdate.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2021 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.lwm2m.client; + +import lombok.Data; + +import java.util.UUID; + +@Data +public class LwM2mSoftwareUpdate { + private volatile String clientSwVersion; + private volatile String currentSwVersion; + private volatile UUID currentSwId; +} \ No newline at end of file diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index a68359a1ca..dbab13d0d1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -29,6 +29,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; import java.security.KeyStore; @@ -136,18 +137,26 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @Getter @Value("${transport.lwm2m.server.security.alias:}") private String certificateAlias; - - + + @Getter + @Value("${transport.lwm2m.log_max_length:}") + private int logMaxLength; + + @PostConstruct public void init() { + URI uri = null; try { - File keyStoreFile = new File(Resources.getResource(keyStorePathFile).toURI()); + uri = Resources.getResource(keyStorePathFile).toURI(); + log.error("URI: {}", uri); + File keyStoreFile = new File(uri); InputStream inKeyStore = new FileInputStream(keyStoreFile); keyStoreValue = KeyStore.getInstance(keyStoreType); keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray()); } catch (Exception e) { log.error("Unable to lookup LwM2M keystore. Reason: " + e.getMessage(), e); - throw new RuntimeException("Failed to lookup LwM2M keystore", e); +// Absence of the key store should not block user from using plain LwM2M +// throw new RuntimeException("Failed to lookup LwM2M keystore: " + (uri != null ? uri.toString() : ""), e); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index 5292c1f9fb..8e059e5a3b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -87,6 +87,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEVICE_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_OBJECT_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; @@ -1420,9 +1421,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler int chunkSize = 0; int chunk = 0; byte[] firmwareChunk = firmwareDataCache.get(lwM2MClient.getFrUpdate().getCurrentFwId().toString(), chunkSize, chunk); - Integer objectId = 5; - String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(objectId); - String targetIdVer = LWM2M_SEPARATOR_PATH + objectId + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0; + String verSupportedObject = lwM2MClient.getRegistration().getSupportedObject().get(FR_OBJECT_ID); + String targetIdVer = LWM2M_SEPARATOR_PATH + FR_OBJECT_ID + LWM2M_SEPARATOR_KEY + verSupportedObject + LWM2M_SEPARATOR_PATH + 0 + LWM2M_SEPARATOR_PATH + 0; lwM2mTransportRequest.sendAllRequest(lwM2MClient.getRegistration(), targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE.getName(), firmwareChunk, config.getTimeout(), null); log.warn("updateFirmwareClient [{}] [{}]", lwM2MClient.getFrUpdate().getCurrentFwVersion(), lwM2MClient.getFrUpdate().getClientFwVersion()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index 418e3d003b..c48fce0bcd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -117,29 +117,29 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { builder.setLocalAddress(config.getHost(), serverPortNoSec); builder.setLocalSecureAddress(config.getSecureHost(), serverSecurePort); builder.setDecoder(new DefaultLwM2mNodeDecoder()); - /** Use a magic converter to support bad type send by the UI. */ + /* Use a magic converter to support bad type send by the UI. */ builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); - /** Create CoAP Config */ + /* Create CoAP Config */ NetworkConfig networkConfig = getCoapConfig(serverPortNoSec, serverSecurePort); BlockwiseLayer blockwiseLayer = new BlockwiseLayer(networkConfig); builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort)); - /** Define model provider (Create Models )*/ + /* Define model provider (Create Models )*/ LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.helper, this.context); config.setModelProvider(modelProvider); builder.setObjectModelProvider(modelProvider); - /** Create credentials */ + /* Create credentials */ this.setServerWithCredentials(builder); - /** Set securityStore with new registrationStore */ + /* Set securityStore with new registrationStore */ builder.setSecurityStore(securityStore); builder.setRegistrationStore(registrationStore); - /** Create DTLS Config */ + /* Create DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setServerOnly(true); dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups()); @@ -156,10 +156,10 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256); } - /** Set DTLS Config */ + /* Set DTLS Config */ builder.setDtlsConfig(dtlsConfig); - /** Create LWM2M server */ + /* Create LWM2M server */ return builder.build(); } @@ -173,10 +173,10 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { trustedCertificates[0] = rootCAX509Cert; builder.setTrustedCertificates(trustedCertificates); } else { - /** by default trust all */ + /* by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); } - /** Set securityStore with registrationStore*/ + /* Set securityStore with registrationStore*/ builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { @Override protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, @@ -189,7 +189,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { this.infoPramsUri("RPK"); this.infoParamsServerKey(this.publicKey, this.privateKey); } else { - /** by default trust all */ + /* by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); log.info("Unable to load X509 files for LWM2MServer"); this.pskMode = true; @@ -256,7 +256,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { } private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { - /** Get Elliptic Curve Parameter spec for secp256r1 */ + /* Get Elliptic Curve Parameter spec for secp256r1 */ AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); algoParameters.init(new ECGenParameterSpec("secp256r1")); ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); @@ -280,17 +280,17 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { } private void infoParamsServerKey(PublicKey publicKey, PrivateKey privateKey) { - /** Get x coordinate */ + /* Get x coordinate */ byte[] x = ((ECPublicKey) publicKey).getW().getAffineX().toByteArray(); if (x[0] == 0) x = Arrays.copyOfRange(x, 1, x.length); - /** Get Y coordinate */ + /* Get Y coordinate */ byte[] y = ((ECPublicKey) publicKey).getW().getAffineY().toByteArray(); if (y[0] == 0) y = Arrays.copyOfRange(y, 1, y.length); - /** Get Curves params */ + /* Get Curves params */ String params = ((ECPublicKey) publicKey).getParams().toString(); String privHex = Hex.encodeHexString(privateKey.getEncoded()); log.info(" \n- Public Key (Hex): [{}] \n" + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java index 4cf12941a5..3de5846ca0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mNetworkConfig.java @@ -23,73 +23,72 @@ public class LwM2mNetworkConfig { NetworkConfig coapConfig = new NetworkConfig(); coapConfig.setInt(NetworkConfig.Keys.COAP_PORT,serverPortNoSec); coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT,serverSecurePort); - /** - * Example:Property for large packet: - * #NetworkConfig config = new NetworkConfig(); - * #config.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE,32); - * #config.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE,32); - * #config.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE,2048); - * #config.setInt(NetworkConfig.Keys.MAX_RETRANSMIT,3); - * #config.setInt(NetworkConfig.Keys.MAX_TRANSMIT_WAIT,120000); + /* + Example:Property for large packet: + #NetworkConfig config = new NetworkConfig(); + #config.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE,32); + #config.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE,32); + #config.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE,2048); + #config.setInt(NetworkConfig.Keys.MAX_RETRANSMIT,3); + #config.setInt(NetworkConfig.Keys.MAX_TRANSMIT_WAIT,120000); */ - /** - * Property to indicate if the response should always include the Block2 option \ - * when client request early blockwise negociation but the response can be sent on one packet. - * - value of false indicate that the server will respond without block2 option if no further blocks are required. - * - value of true indicate that the server will response with block2 option event if no further blocks are required. - * CoAP client will try to use block mode - * or adapt the block size when receiving a 4.13 Entity too large response code + /* + Property to indicate if the response should always include the Block2 option \ + when client request early blockwise negociation but the response can be sent on one packet. + - value of false indicate that the server will respond without block2 option if no further blocks are required. + - value of true indicate that the server will response with block2 option event if no further blocks are required. + CoAP client will try to use block mode + or adapt the block size when receiving a 4.13 Entity too large response code */ coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); - /*** - * Property to indicate if the response should always include the Block2 option \ - * when client request early blockwise negociation but the response can be sent on one packet. - * - value of false indicate that the server will respond without block2 option if no further blocks are required. - * - value of true indicate that the server will response with block2 option event if no further blocks are required. + /* + Property to indicate if the response should always include the Block2 option \ + when client request early blockwise negociation but the response can be sent on one packet. + - value of false indicate that the server will respond without block2 option if no further blocks are required. + - value of true indicate that the server will response with block2 option event if no further blocks are required. */ coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); coapConfig.setInt(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, 300000); - /** - * !!! REQUEST_ENTITY_TOO_LARGE CODE=4.13 - * The maximum size of a resource body (in bytes) that will be accepted - * as the payload of a POST/PUT or the response to a GET request in a - * transparent> blockwise transfer. - * This option serves as a safeguard against excessive memory - * consumption when many resources contain large bodies that cannot be - * transferred in a single CoAP message. This option has no impact on - * *manually* managed blockwise transfers in which the blocks are handled individually. - * Note that this option does not prevent local clients or resource - * implementations from sending large bodies as part of a request or response to a peer. - * The default value of this property is DEFAULT_MAX_RESOURCE_BODY_SIZE = 8192 - * A value of {@code 0} turns off transparent handling of blockwise transfers altogether. + /* + !!! REQUEST_ENTITY_TOO_LARGE CODE=4.13 + The maximum size of a resource body (in bytes) that will be accepted + as the payload of a POST/PUT or the response to a GET request in a + transparent> blockwise transfer. + This option serves as a safeguard against excessive memory + consumption when many resources contain large bodies that cannot be + transferred in a single CoAP message. This option has no impact on + *manually* managed blockwise transfers in which the blocks are handled individually. + Note that this option does not prevent local clients or resource + implementations from sending large bodies as part of a request or response to a peer. + The default value of this property is DEFAULT_MAX_RESOURCE_BODY_SIZE = 8192 + A value of {@code 0} turns off transparent handling of blockwise transfers altogether. */ -// coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 8192); coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024); - /** - * The default DTLS response matcher. - * Supported values are STRICT, RELAXED, or PRINCIPAL. - * The default value is STRICT. - * Create new instance of udp endpoint context matcher. - * Params: - * checkAddress - * – true with address check, (STRICT, UDP) - * - false, without + /* + The default DTLS response matcher. + Supported values are STRICT, RELAXED, or PRINCIPAL. + The default value is STRICT. + Create new instance of udp endpoint context matcher. + Params: + checkAddress + – true with address check, (STRICT, UDP) + - false, without */ coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT"); - /** - * https://tools.ietf.org/html/rfc7959#section-2.9.3 - * The block size (number of bytes) to use when doing a blockwise transfer. \ - * This value serves as the upper limit for block size in blockwise transfers + /* + https://tools.ietf.org/html/rfc7959#section-2.9.3 + The block size (number of bytes) to use when doing a blockwise transfer. \ + This value serves as the upper limit for block size in blockwise transfers */ coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); - /** - * The maximum payload size (in bytes) that can be transferred in a - * single message, i.e. without requiring a blockwise transfer. - * NB: this value MUST be adapted to the maximum message size supported by the transport layer. - * In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol - * DEFAULT_VALUE = 1024 + /* + The maximum payload size (in bytes) that can be transferred in a + single message, i.e. without requiring a blockwise transfer. + NB: this value MUST be adapted to the maximum message size supported by the transport layer. + In particular, this value cannot exceed the network's MTU if UDP is used as the transport protocol + DEFAULT_VALUE = 1024 */ coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index 6a1be12938..3557af6d40 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -404,17 +404,40 @@ public class LwM2mTransportRequest { } } - private void infoWriteResponse(Registration registration, LwM2mResponse response, - DownlinkRequest request) { - LwM2mNode node = ((WriteRequest) request).getNode(); - Object value = this.converter.convertValue(((LwM2mSingleResource) node).getValue(), - ((LwM2mSingleResource) node).getType(), ResourceModel.Type.STRING, request.getPath()); - String msg = String.format("%s: Update finished successfully: CoapCde - %s Lwm2m code - %d name - %s Resource path - %s value - %s", - LOG_LW2M_INFO, ((Response) response.getCoapResponse()).getCode(), response.getCode().getCode(), - response.getCode().getName(), request.getPath().toString(), value); - serviceImpl.sendLogsToThingsboard(msg, registration.getId()); - log.trace("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName().toString(), registration.getEndpoint(), - ((Response) response.getCoapResponse()).getCode(), response.getCode(), - request.getPath().toString(), value); + private void infoWriteResponse(Registration registration, LwM2mResponse response, DownlinkRequest request) { + try { + LwM2mNode node = ((WriteRequest) request).getNode(); + String msg; + Object value; + LwM2mSingleResource singleResource = (LwM2mSingleResource) node; + if (singleResource.getType() == ResourceModel.Type.STRING || singleResource.getType() == ResourceModel.Type.OPAQUE) { + int valueLength; + if (singleResource.getType() == ResourceModel.Type.STRING) { + valueLength = ((String) singleResource.getValue()).length(); + value = ((String) singleResource.getValue()) + .substring(Math.min(valueLength, config.getLogMaxLength())); + + } else { + valueLength = ((byte[]) singleResource.getValue()).length; + value = new String(Arrays.copyOf(((byte[]) singleResource.getValue()), + Math.min(valueLength, config.getLogMaxLength()))); + } + value = valueLength > config.getLogMaxLength() ? value + "..." : value; + msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path - %s length - %s value - %s", + LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), valueLength, value); + } else { + value = this.converter.convertValue(singleResource.getValue(), + singleResource.getType(), ResourceModel.Type.STRING, request.getPath()); + msg = String.format("%s: Update finished successfully: Lwm2m code - %d Resource path - %s value - %s", + LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), value); + } + if (msg != null) { + serviceImpl.sendLogsToThingsboard(msg, registration.getId()); + log.warn("[{}] [{}] [{}] - [{}] [{}] Update finished successfully: [{}]", request.getClass().getName(), registration.getEndpoint(), + ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString(), value); + } + } catch (Exception e) { + log.trace("Fail convert value from request to string. ", e); + } } } From 8a4c0a71ce3e38c3074aef2cb4bd2f32e96f81d2 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 4 May 2021 12:00:16 +0300 Subject: [PATCH 120/127] Reduced warnings --- .../transport/lwm2m/config/LwM2MTransportServerConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index dbab13d0d1..cadb98a766 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -154,7 +154,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { keyStoreValue = KeyStore.getInstance(keyStoreType); keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray()); } catch (Exception e) { - log.error("Unable to lookup LwM2M keystore. Reason: " + e.getMessage(), e); + log.warn("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage()); // Absence of the key store should not block user from using plain LwM2M // throw new RuntimeException("Failed to lookup LwM2M keystore: " + (uri != null ? uri.toString() : ""), e); } From dd5195e8d741a427c44da49f308f7f48a67625c8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 4 May 2021 12:54:31 +0300 Subject: [PATCH 121/127] Refactoring of LwM2MTransport --- ...TransportBootstrapServerConfiguration.java | 66 +++++++++++-------- ...2MTransportBootstrapServerInitializer.java | 49 -------------- .../LwM2MTransportContextBootstrap.java | 54 --------------- .../secure/LwM2MBootstrapConfig.java | 23 ++++--- .../secure/LwM2MBootstrapSecurityStore.java | 28 ++++---- .../LwM2MInMemoryBootstrapConfigStore.java | 4 +- .../LwM2mDefaultBootstrapSessionManager.java | 1 - .../config/LwM2MTransportBootstrapConfig.java | 5 -- .../config/LwM2MTransportServerConfig.java | 6 -- .../secure/LWM2MGenerationPSkRPkECC.java | 41 ++++++------ ...LwM2mCredentialsSecurityInfoValidator.java | 13 ++-- .../lwm2m/secure/LwM2mRPkCredentials.java | 23 +++---- .../lwm2m/secure/ReadResultSecurityStore.java | 8 +-- .../DefaultLwM2MTransportMsgHandler.java | 64 +++++++++--------- .../server/DefaultLwM2mTransportService.java | 21 +++--- .../lwm2m/server/LwM2mServerListener.java | 4 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 24 +++---- .../server/LwM2mTransportMsgHandler.java | 1 - .../lwm2m/server/LwM2mTransportRequest.java | 50 +++++++------- .../server/LwM2mTransportServerHelper.java | 16 ++--- .../LwM2mTransportServerInitializer.java | 33 ---------- ...ndlerUtil.java => LwM2mTransportUtil.java} | 18 ++--- .../server/LwM2mVersionedModelProvider.java | 9 +-- .../lwm2m/server/client/LwM2mClient.java | 8 +-- .../server/client/LwM2mClientContextImpl.java | 8 +-- .../server/client/Lwm2mClientRpcRequest.java | 24 ++++--- .../lwm2m/server/client/ModelObject.java | 1 + .../store/TbLwM2mRedisRegistrationStore.java | 6 +- .../store/TbLwM2mRedisSecurityStore.java | 1 - 29 files changed, 225 insertions(+), 384 deletions(-) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{LwM2mTransportHandlerUtil.java => LwM2mTransportUtil.java} (97%) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java index 052a4f80e1..16bb97aac5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java @@ -15,15 +15,14 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServerBuilder; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapSecurityStore; @@ -31,8 +30,10 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MInMemoryBoot import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; +import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.KeyFactory; @@ -62,38 +63,45 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g @Slf4j @Component -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')") +@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true' && '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')") +@RequiredArgsConstructor public class LwM2MTransportBootstrapServerConfiguration { private PublicKey publicKey; private PrivateKey privateKey; private boolean pskMode = false; - @Autowired - private LwM2MTransportServerConfig serverConfig; + private final LwM2MTransportServerConfig serverConfig; + private final LwM2MTransportBootstrapConfig bootstrapConfig; + private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; + private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; - @Autowired - private LwM2MTransportContextBootstrap contextBs; - - @Autowired - private LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; - - @Autowired - private LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; + private LeshanBootstrapServer server; + @PostConstruct + public void init() { + if (serverConfig.getEnableGenNewKeyPskRpk()) { + new LWM2MGenerationPSkRPkECC(); + } + log.info("Starting LwM2M transport bootstrap server..."); + this.server = getLhBootstrapServer(); + this.server.start(); + log.info("Started LwM2M transport bootstrap server."); + } - @Bean - public LeshanBootstrapServer getLeshanBootstrapServer() { - log.info("Prepare and start BootstrapServer... PostConstruct"); - return this.getLhBootstrapServer(this.contextBs.getCtxBootStrap().getPort(), this.contextBs.getCtxBootStrap().getSecurePort()); + @PreDestroy + public void shutdown() { + log.info("Stopping LwM2M transport bootstrap server!"); + server.destroy(); + log.info("LwM2M transport bootstrap server stopped!"); } - public LeshanBootstrapServer getLhBootstrapServer(Integer bootstrapPortNoSec, Integer bootstrapSecurePort) { + public LeshanBootstrapServer getLhBootstrapServer() { LeshanBootstrapServerBuilder builder = new LeshanBootstrapServerBuilder(); - builder.setLocalAddress(this.contextBs.getCtxBootStrap().getHost(), bootstrapPortNoSec); - builder.setLocalSecureAddress(this.contextBs.getCtxBootStrap().getSecureHost(), bootstrapSecurePort); + builder.setLocalAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); + builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); /** Create CoAP Config */ - builder.setCoapConfig(getCoapConfig(bootstrapPortNoSec, bootstrapSecurePort)); + builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort())); /** Define model provider (Create Models )*/ @@ -169,8 +177,8 @@ public class LwM2MTransportBootstrapServerConfiguration { * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks */ try { - X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(this.contextBs.getCtxBootStrap().getCertificateAlias()); - PrivateKey privateKey = (PrivateKey) serverConfig.getKeyStoreValue().getKey(this.contextBs.getCtxBootStrap().getCertificateAlias(), serverConfig.getKeyStorePassword() == null ? null : serverConfig.getKeyStorePassword().toCharArray()); + X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(this.bootstrapConfig.getCertificateAlias()); + PrivateKey privateKey = (PrivateKey) serverConfig.getKeyStoreValue().getKey(this.bootstrapConfig.getCertificateAlias(), serverConfig.getKeyStorePassword() == null ? null : serverConfig.getKeyStorePassword().toCharArray()); PublicKey publicKey = serverCertificate.getPublicKey(); if (privateKey != null && privateKey.getEncoded().length > 0 && publicKey != null && publicKey.getEncoded().length > 0) { builder.setPublicKey(serverCertificate.getPublicKey()); @@ -201,10 +209,10 @@ public class LwM2MTransportBootstrapServerConfiguration { private void infoPramsUri(String mode) { log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]", mode, - this.contextBs.getCtxBootStrap().getHost(), - this.contextBs.getCtxBootStrap().getPort(), - this.contextBs.getCtxBootStrap().getSecureHost(), - this.contextBs.getCtxBootStrap().getSecurePort()); + this.bootstrapConfig.getHost(), + this.bootstrapConfig.getPort(), + this.bootstrapConfig.getSecureHost(), + this.bootstrapConfig.getSecurePort()); } @@ -238,7 +246,7 @@ public class LwM2MTransportBootstrapServerConfiguration { AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); algoParameters.init(new ECGenParameterSpec("secp256r1")); ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - LwM2MTransportBootstrapConfig serverConfig = this.contextBs.getCtxBootStrap(); + LwM2MTransportBootstrapConfig serverConfig = this.bootstrapConfig; if (StringUtils.isNotEmpty(serverConfig.getPublicX()) && StringUtils.isNotEmpty(serverConfig.getPublicY())) { /** Get point values */ byte[] publicX = Hex.decodeHex(serverConfig.getPublicX().toCharArray()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java deleted file mode 100644 index c3d6d9d0c1..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerInitializer.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright © 2016-2021 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.lwm2m.bootstrap; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.server.californium.bootstrap.LeshanBootstrapServer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Service; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -@Slf4j -@Service -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true') || ('${service.type:null}'=='monolith' && '${transport.lwm2m.enabled:false}'=='true'&& '${transport.lwm2m.bootstrap.enable:false}'=='true')") -public class LwM2MTransportBootstrapServerInitializer { - - @Autowired(required = false) - private LeshanBootstrapServer lhBServer; - - @Autowired - private LwM2MTransportContextBootstrap contextBS; - - @PostConstruct - public void init() { - this.lhBServer.start(); - } - - @PreDestroy - public void shutdown() throws InterruptedException { - log.info("Stopping LwM2M transport Bootstrap Server!"); - lhBServer.destroy(); - log.info("LwM2M transport Bootstrap Server stopped!"); - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java deleted file mode 100644 index 35dff0181d..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportContextBootstrap.java +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright © 2016-2021 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.lwm2m.bootstrap; -/** - * 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 lombok.extern.slf4j.Slf4j; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.TransportContext; -import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; - - -@Slf4j -@Component -@ConditionalOnExpression("('${service.type:null}'=='tb-transport' && '${transport.lwm2m.enabled:false}'=='true') || '${service.type:null}'=='monolith'") -public class LwM2MTransportContextBootstrap extends TransportContext { - - private final LwM2MTransportBootstrapConfig lwM2MTransportBootstrapConfig; - - public LwM2MTransportContextBootstrap(LwM2MTransportBootstrapConfig ctxBootStrap) { - this.lwM2MTransportBootstrapConfig = ctxBootStrap; - } - - public LwM2MTransportBootstrapConfig getCtxBootStrap() { - return this.lwM2MTransportBootstrapConfig; - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java index eba61af24c..937257b189 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java @@ -25,12 +25,12 @@ import java.nio.charset.StandardCharsets; @Data public class LwM2MBootstrapConfig { - /** - * interface BootstrapSecurityConfig - * servers: BootstrapServersSecurityConfig, - * bootstrapServer: ServerSecurityConfig, - * lwm2mServer: ServerSecurityConfig - * } + /* + interface BootstrapSecurityConfig + servers: BootstrapServersSecurityConfig, + bootstrapServer: ServerSecurityConfig, + lwm2mServer: ServerSecurityConfig + } */ /** -servers * shortId: number, @@ -60,10 +60,10 @@ public class LwM2MBootstrapConfig { public BootstrapConfig getLwM2MBootstrapConfig() { BootstrapConfig configBs = new BootstrapConfig(); - /** Delete old security objects */ + /* Delete old security objects */ configBs.toDelete.add("/0"); configBs.toDelete.add("/1"); - /** Server Configuration (object 1) as defined in LWM2M 1.0.x TS. */ + /* Server Configuration (object 1) as defined in LWM2M 1.0.x TS. */ BootstrapConfig.ServerConfig server0 = new BootstrapConfig.ServerConfig(); server0.shortId = servers.getShortId(); server0.lifetime = servers.getLifetime(); @@ -71,10 +71,10 @@ public class LwM2MBootstrapConfig { server0.notifIfDisabled = servers.isNotifIfDisabled(); server0.binding = BindingMode.valueOf(servers.getBinding()); configBs.servers.put(0, server0); - /** Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */ + /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Bootstrap instance = 0 */ this.bootstrapServer.setBootstrapServerIs(true); configBs.security.put(0, setServerSecuruty(this.bootstrapServer.getHost(), this.bootstrapServer.getPort(), this.bootstrapServer.isBootstrapServerIs(), this.bootstrapServer.getSecurityMode(), this.bootstrapServer.getClientPublicKeyOrId(), this.bootstrapServer.getServerPublicKey(), this.bootstrapServer.getClientSecretKey(), this.bootstrapServer.getServerId())); - /** Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */ + /* Security Configuration (object 0) as defined in LWM2M 1.0.x TS. Server instance = 1 */ configBs.security.put(1, setServerSecuruty(this.lwm2mServer.getHost(), this.lwm2mServer.getPort(), this.lwm2mServer.isBootstrapServerIs(), this.lwm2mServer.getSecurityMode(), this.lwm2mServer.getClientPublicKeyOrId(), this.lwm2mServer.getServerPublicKey(), this.lwm2mServer.getClientSecretKey(), this.lwm2mServer.getServerId())); return configBs; } @@ -92,9 +92,8 @@ public class LwM2MBootstrapConfig { } private byte[] setPublicKeyOrId(String publicKeyOrIdStr, String securityMode) { - byte[] publicKey = (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} : + return (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} : SecurityMode.valueOf(securityMode).equals(SecurityMode.PSK) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) : Hex.decodeHex(publicKeyOrIdStr.toCharArray()); - return publicKey; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index 9376236af5..d1e136d6f3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -36,7 +36,7 @@ import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.io.IOException; import java.security.GeneralSecurityException; @@ -44,12 +44,12 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.BOOTSTRAP_SERVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_SERVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVERS; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getBootstrapParametersFromThingsboard; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.BOOTSTRAP_SERVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_SERVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SERVERS; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard; @Slf4j @Service("LwM2MBootstrapSecurityStore") @@ -72,9 +72,9 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { - /** add value to store from BootstrapJson */ + /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfigNew = store.getBootstrapConfig(); if (bsConfigNew != null) { @@ -96,9 +96,9 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { - /** add value to store from BootstrapJson */ + /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfig = store.getBootstrapConfig(); if (bsConfig.security != null) { @@ -114,12 +114,12 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } private void setBootstrapConfigScurityInfo(ReadResultSecurityStore store) { - /** BootstrapConfig */ + /* BootstrapConfig */ LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); if (lwM2MBootstrapConfig != null) { - /** Security info */ + /* Security info */ switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) { - /** Use RPK only */ + /* Use RPK only */ case PSK: store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndPoint(), lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(), diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java index b20954cf7d..bc44922fcf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MInMemoryBootstrapConfigStore.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.server.bootstrap.InMemoryBootstrapConfigStore; import org.eclipse.leshan.server.bootstrap.InvalidConfigurationException; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; + import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; @@ -58,8 +59,7 @@ public class LwM2MInMemoryBootstrapConfigStore extends InMemoryBootstrapConfigSt public BootstrapConfig remove(String enpoint) { writeLock.lock(); try { - BootstrapConfig res = super.remove(enpoint); - return res; + return super.remove(enpoint); } finally { writeLock.unlock(); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java index 34497069f5..bfe8b9ce4f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java @@ -24,7 +24,6 @@ import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityChecker; import org.eclipse.leshan.server.security.SecurityInfo; -import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java index ea4f7ad391..c0379f9cee 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportBootstrapConfig.java @@ -16,15 +16,10 @@ package org.thingsboard.server.transport.lwm2m.config; import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; -import org.thingsboard.server.gen.transport.TransportProtos; - -import java.security.PublicKey; -import java.util.Map; @Slf4j @Component diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java index cadb98a766..cd8287da4c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2MTransportServerConfig.java @@ -27,15 +27,9 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import java.io.File; import java.io.FileInputStream; -import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.nio.file.Path; -import java.nio.file.Paths; import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; @Slf4j @Component diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java index 4df520543b..b99192ece3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java @@ -17,14 +17,15 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.util.Hex; -import java.security.SecureRandom; -import java.security.KeyPairGenerator; + +import java.security.InvalidAlgorithmParameterException; import java.security.KeyPair; -import java.security.PrivateKey; -import java.security.PublicKey; +import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; -import java.security.InvalidAlgorithmParameterException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.util.Arrays; @@ -48,33 +49,31 @@ public class LWM2MGenerationPSkRPkECC { } private void generationPSkKey() { - /** PSK */ + /* PSK */ int lenPSkKey = 32; - /** Start PSK - * Clients and Servers MUST support PSK keys of up to 64 bytes in length, as required by [RFC7925] - * SecureRandom object must be unpredictable, and all SecureRandom output sequences must be cryptographically strong, as described in [RFC4086] - * */ + /* Start PSK + Clients and Servers MUST support PSK keys of up to 64 bytes in length, as required by [RFC7925] + SecureRandom object must be unpredictable, and all SecureRandom output sequences must be cryptographically strong, as described in [RFC4086] + */ SecureRandom randomPSK = new SecureRandom(); - byte bytesPSK[] = new byte[lenPSkKey]; + byte[] bytesPSK = new byte[lenPSkKey]; randomPSK.nextBytes(bytesPSK); log.info("\nCreating new PSK: \n for the next start PSK -> security key: [{}]", Hex.encodeHexString(bytesPSK)); } private void generationRPKECCKey() { - /** RPK */ + /* RPK */ String algorithm = "EC"; String provider = "SunEC"; String nameParameterSpec = "secp256r1"; - /** Start RPK - * Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)] - * */ + /* Start RPK + Elliptic Curve parameters : [secp256r1 [NIST P-256, X9.62 prime256v1] (1.2.840.10045.3.1.7)] + */ KeyPairGenerator kpg = null; try { kpg = KeyPairGenerator.getInstance(algorithm, provider); - } catch (NoSuchAlgorithmException e) { - log.error("", e); - } catch (NoSuchProviderException e) { + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { log.error("", e); } ECGenParameterSpec ecsp = new ECGenParameterSpec(nameParameterSpec); @@ -90,17 +89,17 @@ public class LWM2MGenerationPSkRPkECC { if (pubKey instanceof ECPublicKey) { ECPublicKey ecPublicKey = (ECPublicKey) pubKey; - /** Get x coordinate */ + /* Get x coordinate */ byte[] x = ecPublicKey.getW().getAffineX().toByteArray(); if (x[0] == 0) x = Arrays.copyOfRange(x, 1, x.length); - /** Get Y coordinate */ + /* Get Y coordinate */ byte[] y = ecPublicKey.getW().getAffineY().toByteArray(); if (y[0] == 0) y = Arrays.copyOfRange(y, 1, y.length); - /** Get Curves params */ + /* Get Curves params */ String privHex = Hex.encodeHexString(privKey.getEncoded()); log.info("\nCreating new RPK for the next start... \n" + " Public Key (Hex): [{}]\n" + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index cbba4eb490..7154a234a4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -29,8 +29,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MC import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.io.IOException; import java.security.GeneralSecurityException; @@ -59,7 +58,7 @@ public class LwM2mCredentialsSecurityInfoValidator { * @param keyValue - * @return ValidateDeviceCredentialsResponseMsg and SecurityInfo */ - public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportHandlerUtil.LwM2mTypeServer keyValue) { + public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), @@ -69,7 +68,7 @@ public class LwM2mCredentialsSecurityInfoValidator { String credentialsBody = msg.getCredentialsBody(); resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); - Optional deviceProfileOpt = LwM2mTransportHandlerUtil.decode(msg.getProfileBody().toByteArray()); + Optional deviceProfileOpt = LwM2mTransportUtil.decode(msg.getProfileBody().toByteArray()); deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile)); latch.countDown(); } @@ -96,9 +95,9 @@ public class LwM2mCredentialsSecurityInfoValidator { * @param keyValue - * @return SecurityInfo */ - private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, LwM2mTransportHandlerUtil.LwM2mTypeServer keyValue) { + private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) { ReadResultSecurityStore result = new ReadResultSecurityStore(); - JsonObject objectMsg = LwM2mTransportHandlerUtil.validateJson(jsonStr); + JsonObject objectMsg = LwM2mTransportUtil.validateJson(jsonStr); if (objectMsg != null && !objectMsg.isJsonNull()) { JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null; /** @@ -109,7 +108,7 @@ public class LwM2mCredentialsSecurityInfoValidator { && objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null; endPoint = (endPointPsk == null || endPointPsk.isEmpty()) ? endPoint : endPointPsk; if (object != null && !object.isJsonNull()) { - if (keyValue.equals(LwM2mTransportHandlerUtil.LwM2mTypeServer.BOOTSTRAP)) { + if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { result.setBootstrapJsonCredential(object); result.setEndPoint(endPoint); result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mRPkCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mRPkCredentials.java index 42ed0dbee2..47fa2f8090 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mRPkCredentials.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mRPkCredentials.java @@ -18,20 +18,21 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.util.Hex; + import java.math.BigInteger; -import java.security.PrivateKey; -import java.security.PublicKey; import java.security.AlgorithmParameters; -import java.security.KeyFactory; import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.security.spec.ECGenParameterSpec; import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.KeySpec; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPoint; import java.util.List; @Slf4j @@ -54,7 +55,7 @@ public class LwM2mRPkCredentials { private void generatePublicKeyRPK(String publX, String publY, String privS) { try { - /**Get Elliptic Curve Parameter spec for secp256r1 */ + /*Get Elliptic Curve Parameter spec for secp256r1 */ AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); algoParameters.init(new ECGenParameterSpec("secp256r1")); ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); @@ -62,18 +63,18 @@ public class LwM2mRPkCredentials { // Get point values byte[] publicX = Hex.decodeHex(publX.toCharArray()); byte[] publicY = Hex.decodeHex(publY.toCharArray()); - /** Create key specs */ + /* Create key specs */ KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), parameterSpec); - /** Get keys */ + /* Get keys */ this.serverPublicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); } if (privS != null && !privS.isEmpty()) { - /** Get point values */ + /* Get point values */ byte[] privateS = Hex.decodeHex(privS.toCharArray()); - /** Create key specs */ + /* Create key specs */ KeySpec privateKeySpec = new ECPrivateKeySpec(new BigInteger(privateS), parameterSpec); - /** Get keys */ + /* Get keys */ this.serverPrivateKey = KeyFactory.getInstance("EC").generatePrivate(privateKeySpec); } } catch (GeneralSecurityException | IllegalArgumentException e) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java index d12247467f..30f15116af 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java @@ -31,8 +31,8 @@ public class ReadResultSecurityStore { private int securityMode = DEFAULT_MODE.code; /** bootstrap */ - DeviceProfile deviceProfile; - JsonObject bootstrapJsonCredential; - String endPoint; - BootstrapConfig bootstrapConfig; + private DeviceProfile deviceProfile; + private JsonObject bootstrapJsonCredential; + private String endPoint; + private BootstrapConfig bootstrapConfig; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index 8e059e5a3b..4ab206e856 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -33,7 +33,6 @@ import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.util.NamedThreadFactory; -import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -55,6 +54,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; @@ -85,30 +85,30 @@ import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.CLIENT_NOT_AUTHORIZED; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEVICE_ATTRIBUTES_REQUEST; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_OBJECT_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_STRATEGY_2; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.DISCOVER; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.EXECUTE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_CANCEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_READ_ALL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.READ; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_REPLACE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_UPDATE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVICE_CHANNEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertJsonArrayToSet; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getAckCallback; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.validateObjectVerFromKey; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.CLIENT_NOT_AUTHORIZED; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEVICE_ATTRIBUTES_REQUEST; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_OBJECT_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_PATH_RESOURCE_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_STRATEGY_2; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.READ; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SERVICE_CHANNEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getAckCallback; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey; @Slf4j @Service @@ -125,6 +125,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler private final LwM2MTransportServerConfig config; private final FirmwareDataCache firmwareDataCache; private final LwM2mTransportServerHelper helper; + private final LwM2MJsonAdaptor adaptor; private final LwM2mClientContext lwM2mClientContext; private final LwM2mTransportRequest lwM2mTransportRequest; @@ -132,7 +133,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler LwM2mClientContext lwM2mClientContext, @Lazy LwM2mTransportRequest lwM2mTransportRequest, FirmwareDataCache firmwareDataCache, - LwM2mTransportContext context) { + LwM2mTransportContext context, LwM2MJsonAdaptor adaptor) { this.transportService = transportService; this.config = config; this.helper = helper; @@ -140,6 +141,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler this.lwM2mTransportRequest = lwM2mTransportRequest; this.firmwareDataCache = firmwareDataCache; this.context = context; + this.adaptor = adaptor; } @PostConstruct @@ -334,7 +336,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); - Object valueNew = this.helper.getValueFromKvProto(tsKvProto.getKv()); + Object valueNew = LwM2mTransportServerHelper.getValueFromKvProto(tsKvProto.getKv()); //TODO: react on change of the firmware name. if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { this.getInfoFirmwareUpdate(lwM2MClient); @@ -361,7 +363,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } else if (msg.getSharedDeletedCount() > 0) { msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); - Object valueNew = this.helper.getValueFromKvProto(tsKvProto.getKv()); + Object valueNew = LwM2mTransportServerHelper.getValueFromKvProto(tsKvProto.getKv()); if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew); } @@ -458,7 +460,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler lwm2mClientRpcRequest.setRequestId(toDeviceRequest.getRequestId()); lwm2mClientRpcRequest.setSessionInfo(sessionInfo); lwm2mClientRpcRequest.setValidTypeOper(toDeviceRequest.getMethodName()); - JsonObject rpcRequest = LwM2mTransportHandlerUtil.validateJson(toDeviceRequest.getParams()); + JsonObject rpcRequest = LwM2mTransportUtil.validateJson(toDeviceRequest.getParams()); if (rpcRequest != null) { if (rpcRequest.has(lwm2mClientRpcRequest.keyNameKey)) { String targetIdVer = this.getPresentPathIntoProfile(sessionInfo, @@ -639,7 +641,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); Set clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); if (clientObjects != null && clientObjects.size() > 0) { - if (LWM2M_STRATEGY_2 == LwM2mTransportHandlerUtil.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { + if (LWM2M_STRATEGY_2 == LwM2mTransportUtil.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { // #2 lwM2MClient.getPendingReadRequests().addAll(clientObjects); clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(registration, path, READ, ContentFormat.TLV.getName(), @@ -1361,7 +1363,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler if (keyNamesMap.values().size() > 0) { try { //#1.2 - TransportProtos.GetAttributeRequestMsg getAttributeMsg = helper.getAdaptor().convertToGetAttributes(null, keyNamesMap.values()); + TransportProtos.GetAttributeRequestMsg getAttributeMsg = adaptor.convertToGetAttributes(null, keyNamesMap.values()); transportService.process(sessionInfo, getAttributeMsg, getAckCallback(lwM2MClient, getAttributeMsg.getRequestId(), DEVICE_ATTRIBUTES_REQUEST)); } catch (AdaptorException e) { log.warn("Failed to decode get attributes request", e); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java index c48fce0bcd..5879b82a33 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mTransportService.java @@ -17,8 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.network.config.NetworkConfig; -import org.eclipse.californium.core.network.stack.BlockwiseLayer; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; @@ -91,40 +89,39 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { if (config.getEnableGenNewKeyPskRpk()) { new LWM2MGenerationPSkRPkECC(); } - this.server = getLhServer(config.getPort(), config.getSecurePort()); + this.server = getLhServer(); this.startLhServer(); this.context.setServer(server); } private void startLhServer() { - log.info("Starting LwM2M transport Server..."); + log.info("Starting LwM2M transport server..."); this.server.start(); LwM2mServerListener lhServerCertListener = new LwM2mServerListener(handler); this.server.getRegistrationService().addListener(lhServerCertListener.registrationListener); this.server.getPresenceService().addListener(lhServerCertListener.presenceListener); this.server.getObservationService().addListener(lhServerCertListener.observationListener); + log.info("Started LwM2M transport server."); } @PreDestroy public void shutdown() { - log.info("Stopping LwM2M transport Server!"); + log.info("Stopping LwM2M transport server!"); server.destroy(); - log.info("LwM2M transport Server stopped!"); + log.info("LwM2M transport server stopped!"); } - private LeshanServer getLhServer(Integer serverPortNoSec, Integer serverSecurePort) { + private LeshanServer getLhServer() { LeshanServerBuilder builder = new LeshanServerBuilder(); - builder.setLocalAddress(config.getHost(), serverPortNoSec); - builder.setLocalSecureAddress(config.getSecureHost(), serverSecurePort); + builder.setLocalAddress(config.getHost(), config.getPort()); + builder.setLocalSecureAddress(config.getSecureHost(), config.getSecurePort()); builder.setDecoder(new DefaultLwM2mNodeDecoder()); /* Use a magic converter to support bad type send by the UI. */ builder.setEncoder(new DefaultLwM2mNodeEncoder(LwM2mValueConverterImpl.getInstance())); /* Create CoAP Config */ - NetworkConfig networkConfig = getCoapConfig(serverPortNoSec, serverSecurePort); - BlockwiseLayer blockwiseLayer = new BlockwiseLayer(networkConfig); - builder.setCoapConfig(getCoapConfig(serverPortNoSec, serverSecurePort)); + builder.setCoapConfig(getCoapConfig(config.getPort(), config.getSecurePort())); /* Define model provider (Create Models )*/ LwM2mModelProvider modelProvider = new LwM2mVersionedModelProvider(this.lwM2mClientContext, this.helper, this.context); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java index f4084708c3..f46b1c05f7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mServerListener.java @@ -26,8 +26,8 @@ import org.eclipse.leshan.server.registration.RegistrationUpdate; import java.util.Collection; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @Slf4j public class LwM2mServerListener { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java index 3a9114aa43..5d6486d9a2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mSessionMsgListener.java @@ -35,22 +35,22 @@ import java.util.Optional; @Slf4j public class LwM2mSessionMsgListener implements GenericFutureListener>, SessionMsgListener { - private DefaultLwM2MTransportMsgHandler service; + private DefaultLwM2MTransportMsgHandler handler; private TransportProtos.SessionInfoProto sessionInfo; - public LwM2mSessionMsgListener(DefaultLwM2MTransportMsgHandler service, TransportProtos.SessionInfoProto sessionInfo) { - this.service = service; + public LwM2mSessionMsgListener(DefaultLwM2MTransportMsgHandler handler, TransportProtos.SessionInfoProto sessionInfo) { + this.handler = handler; this.sessionInfo = sessionInfo; } @Override public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { - this.service.onGetAttributesResponse(getAttributesResponse, this.sessionInfo); + this.handler.onGetAttributesResponse(getAttributesResponse, this.sessionInfo); } @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { - this.service.onAttributeUpdate(attributeUpdateNotification, this.sessionInfo); + this.handler.onAttributeUpdate(attributeUpdateNotification, this.sessionInfo); } @Override @@ -60,27 +60,27 @@ public class LwM2mSessionMsgListener implements GenericFutureListener deviceProfileOpt) { - this.service.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); + this.handler.onDeviceUpdate(sessionInfo, device, deviceProfileOpt); } @Override public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { - this.service.onToDeviceRpcRequest(toDeviceRequest,this.sessionInfo); + this.handler.onToDeviceRpcRequest(toDeviceRequest,this.sessionInfo); } @Override public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { - this.service.onToServerRpcResponse(toServerResponse); + this.handler.onToServerRpcResponse(toServerResponse); } @Override @@ -91,14 +91,14 @@ public class LwM2mSessionMsgListener implements GenericFutureListener resourceUpdateMsgOpt) { if (ResourceType.LWM2M_MODEL.name().equals(resourceUpdateMsgOpt.get().getResourceType())) { - this.service.onResourceUpdate(resourceUpdateMsgOpt); + this.handler.onResourceUpdate(resourceUpdateMsgOpt); } } @Override public void onResourceDelete(@NotNull Optional resourceDeleteMsgOpt) { if (ResourceType.LWM2M_MODEL.name().equals(resourceDeleteMsgOpt.get().getResourceType())) { - this.service.onResourceDelete(resourceDeleteMsgOpt); + this.handler.onResourceDelete(resourceDeleteMsgOpt); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java index d620492173..bb0be3aa36 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java @@ -20,7 +20,6 @@ import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.Lwm2mClientRpcRequest; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index 3557af6d40..4e0b0740b1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -46,10 +46,8 @@ import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.NamedThreadFactory; -import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @@ -68,18 +66,18 @@ import java.util.stream.Collectors; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.CONTENT; import static org.eclipse.leshan.core.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.ResponseCode.NOT_FOUND; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.DEFAULT_TIMEOUT; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.FR_PATH_RESOURCE_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_CANCEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE_READ_ALL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.RESPONSE_CHANNEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.createWriteAttributeRequest; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEFAULT_TIMEOUT; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_PATH_RESOURCE_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_CHANNEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.createWriteAttributeRequest; @Slf4j @Service @@ -92,7 +90,6 @@ public class LwM2mTransportRequest { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - private final LwM2mTransportServerHelper lwM2MTransportServerHelper; private final LwM2mClientContext lwM2mClientContext; private final DefaultLwM2MTransportMsgHandler serviceImpl; @@ -120,8 +117,7 @@ public class LwM2mTransportRequest { ContentFormat contentFormat = contentFormatName != null ? ContentFormat.fromName(contentFormatName.toUpperCase()) : ContentFormat.DEFAULT; LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2mClientWithReg(registration, null); LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null; - if (!OBSERVE_READ_ALL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && - lwM2MClient != null) { + if (!OBSERVE_READ_ALL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && lwM2MClient != null) { if (lwM2MClient.isValidObjectVersion(targetIdVer)) { timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; ResourceModel resourceModel = null; @@ -142,10 +138,10 @@ public class LwM2mTransportRequest { } break; case OBSERVE_CANCEL: - /** - * lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); - * At server side this will not remove the observation from the observation store, to do it you need to use - * {@code ObservationService#cancelObservation()} + /* + lwM2MTransportRequest.sendAllRequest(lwServer, registration, path, POST_TYPE_OPER_OBSERVE_CANCEL, null, null, null, null, context.getTimeout()); + At server side this will not remove the observation from the observation store, to do it you need to use + {@code ObservationService#cancelObservation()} */ context.getServer().getObservationService().cancelObservations(registration, target); break; @@ -266,9 +262,9 @@ public class LwM2mTransportRequest { if (rpcRequest != null) { serviceImpl.sentRpcRequest(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR); } - /** Not Found - * set setClient_fw_version = empty - **/ + /* Not Found + set setClient_fw_version = empty + */ if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) { lwM2MClient.setUpdateFw(false); lwM2MClient.getFrUpdate().setClientFwVersion(""); @@ -277,9 +273,9 @@ public class LwM2mTransportRequest { } } }, e -> { - /** version == null - * set setClient_fw_version = empty - **/ + /* version == null + set setClient_fw_version = empty + */ if (FR_PATH_RESOURCE_VER_ID.equals(request.getPath().toString()) && lwM2MClient.isUpdateFw()) { lwM2MClient.setUpdateFw(false); lwM2MClient.getFrUpdate().setClientFwVersion(""); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java index 210f2451a7..79902348a9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerHelper.java @@ -30,7 +30,6 @@ package org.thingsboard.server.transport.lwm2m.server; * limitations under the License. */ -import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.DDFFileParser; @@ -40,7 +39,6 @@ import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.codec.CodecException; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; @@ -55,7 +53,7 @@ import java.util.ArrayList; import java.util.List; import static org.thingsboard.server.gen.transport.TransportProtos.KeyValueType.BOOLEAN_V; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY; @Slf4j @Component @@ -64,10 +62,6 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle public class LwM2mTransportServerHelper { private final LwM2mTransportContext context; - - private final TransportService transportService; - - @Getter private final LwM2MJsonAdaptor adaptor; /** @@ -95,7 +89,7 @@ public class LwM2mTransportServerHelper { request.addAllKv(result); PostAttributeMsg postAttributeMsg = request.build(); TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postAttributeMsg); - transportService.process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call)); + context.getTransportService().process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call)); } public void sendParametersOnThingsboardTelemetry(List result, SessionInfoProto sessionInfo) { @@ -106,7 +100,7 @@ public class LwM2mTransportServerHelper { request.addTsKvList(builder.build()); PostTelemetryMsg postTelemetryMsg = request.build(); TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postTelemetryMsg); - transportService.process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call)); + context.getTransportService().process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call)); } /** @@ -191,7 +185,7 @@ public class LwM2mTransportServerHelper { * @param resourcePath - * @return */ - public ResourceModel.Type getResourceModelTypeEqualsKvProtoValueType(ResourceModel.Type currentType, String resourcePath) { + public static ResourceModel.Type getResourceModelTypeEqualsKvProtoValueType(ResourceModel.Type currentType, String resourcePath) { switch (currentType) { case BOOLEAN: return ResourceModel.Type.BOOLEAN; @@ -209,7 +203,7 @@ public class LwM2mTransportServerHelper { throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType); } - public Object getValueFromKvProto(TransportProtos.KeyValueProto kv) { + public static Object getValueFromKvProto(TransportProtos.KeyValueProto kv) { switch (kv.getType()) { case BOOLEAN_V: return kv.getBoolV(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java deleted file mode 100644 index 1b1ace3c5b..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportServerInitializer.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright © 2016-2021 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.lwm2m.server; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.server.californium.LeshanServer; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; -import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; - -@Slf4j -@Component("LwM2MTransportServerInitializer") -@TbLwM2mTransportComponent -public class LwM2mTransportServerInitializer { - -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java similarity index 97% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java index a5780d9879..6a8e74f229 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java @@ -67,7 +67,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; @Slf4j -public class LwM2mTransportHandlerUtil { +public class LwM2mTransportUtil { public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0"; public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; @@ -150,8 +150,8 @@ public class LwM2mTransportHandlerUtil { * if all resources are to be replaced */ WRITE_REPLACE(6, "WriteReplace"), - /** - * PUT + /* + PUT */ /** * Adds or updates Resources provided in the new value and leaves other existing Resources unchanged. (see section @@ -178,7 +178,7 @@ public class LwM2mTransportHandlerUtil { return to; } } - throw new IllegalArgumentException(String.format("Unsupported typeOper type : %d", type)); + throw new IllegalArgumentException(String.format("Unsupported typeOper type : %s", type)); } } @@ -198,7 +198,7 @@ public class LwM2mTransportHandlerUtil { case OBJLNK: return valueOld.equals(valueNew); case OPAQUE: - return Hex.decodeHex(((String) valueOld).toCharArray()).equals(Hex.decodeHex(((String) valueNew).toCharArray())); + return Arrays.equals(Hex.decodeHex(((String) valueOld).toCharArray()), Hex.decodeHex(((String) valueNew).toCharArray())); default: throw new CodecException("Invalid value type for resource %s, type %s", resourcePath, type); } @@ -256,7 +256,7 @@ public class LwM2mTransportHandlerUtil { ObjectMapper mapper = new ObjectMapper(); String profileStr = mapper.writeValueAsString(profile); JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; - return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportHandlerUtil.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; + return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportUtil.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; } catch (IOException e) { log.error("", e); } @@ -415,7 +415,7 @@ public class LwM2mTransportHandlerUtil { } public static String validPathIdVer(String pathIdVer, Registration registration) throws IllegalArgumentException { - if (pathIdVer.indexOf(LWM2M_SEPARATOR_PATH) < 0) { + if (!pathIdVer.contains(LWM2M_SEPARATOR_PATH)) { throw new IllegalArgumentException(String.format("Error:")); } else { String[] keyArray = pathIdVer.split(LWM2M_SEPARATOR_PATH); @@ -488,7 +488,7 @@ public class LwM2mTransportHandlerUtil { } private static Attribute[] createWriteAttributes(Object params) { - List attributeLists = new ArrayList(); + List attributeLists = new ArrayList<>(); ObjectMapper oMapper = new ObjectMapper(); Map map = oMapper.convertValue(params, ConcurrentHashMap.class); map.forEach((k, v) -> { @@ -498,7 +498,7 @@ public class LwM2mTransportHandlerUtil { ((Double) v).longValue() : v)); } }); - return (Attribute[]) attributeLists.toArray(Attribute[]::new); + return attributeLists.toArray(Attribute[]::new); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java index bb27b66d69..b1c8eeec6b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java @@ -30,7 +30,6 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import java.util.ArrayList; import java.util.Base64; import java.util.Collection; -import java.util.Iterator; import java.util.Map; import java.util.Optional; @@ -51,7 +50,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { private final LwM2mTransportServerHelper helper; private final LwM2mTransportContext context; - private String getKeyIdVer(Integer objectId, String version) { + private String getKeyIdVer(Integer objectId, String version) { return objectId != null ? objectId + LWM2M_SEPARATOR_KEY + ((version == null || version.isEmpty()) ? ObjectModel.DEFAULT_VERSION : version) : null; } @@ -84,7 +83,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { return objectModel.resources.get(resourceId); else log.warn("TbResources (Object model) with id [{}/0/{}] not found on the server", objectId, resourceId); - return null; + return null; } catch (Exception e) { log.error("", e); return null; @@ -104,9 +103,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { public Collection getObjectModels() { Map supportedObjects = this.registration.getSupportedObject(); Collection result = new ArrayList<>(supportedObjects.size()); - Iterator> i$ = supportedObjects.entrySet().iterator(); - while (i$.hasNext()) { - Map.Entry supportedObject = i$.next(); + for (Map.Entry supportedObject : supportedObjects.entrySet()) { ObjectModel objectModel = this.getObjectModelDynamic(supportedObject.getKey(), supportedObject.getValue()); if (objectModel != null) { result.add(objectModel); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index df212790da..8577e177c4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -26,8 +26,8 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.Collection; @@ -42,9 +42,9 @@ import java.util.concurrent.CopyOnWriteArrayList; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.TRANSPORT_DEFAULT_LWM2M_VERSION; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getVerFromPathIdVerOrId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId; @Slf4j @Data diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index 44df451be7..d209d8d829 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -25,7 +25,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.util.Arrays; import java.util.Map; @@ -34,7 +34,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @Service @TbLwM2mTransportComponent @@ -118,7 +118,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { */ @Override public LwM2mClient addLwM2mClientToSession(String identity) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportHandlerUtil.LwM2mTypeServer.CLIENT); + ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; LwM2mClient client; @@ -165,7 +165,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { - LwM2mClientProfile lwM2MClientProfile = LwM2mTransportHandlerUtil.getLwM2MClientProfileFromThingsboard(deviceProfile); + LwM2mClientProfile lwM2MClientProfile = LwM2mTransportUtil.getLwM2MClientProfileFromThingsboard(deviceProfile); if (lwM2MClientProfile != null) { profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); return true; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java index d2c6ada458..4734b5dd8e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java @@ -21,11 +21,11 @@ import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; import java.util.concurrent.ConcurrentHashMap; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.validPathIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validPathIdVer; @Data public class Lwm2mClientRpcRequest { @@ -43,7 +43,7 @@ public class Lwm2mClientRpcRequest { private LwM2mTypeOper typeOper; private String targetIdVer; - private String contentFormatName; + private String contentFormatName; private long timeoutInMs; private Object value; private ConcurrentHashMap params; @@ -54,19 +54,19 @@ public class Lwm2mClientRpcRequest { private String infoMsg; private String responseCode; - public void setValidTypeOper (String typeOper){ + public void setValidTypeOper(String typeOper) { try { this.typeOper = LwM2mTypeOper.fromLwLwM2mTypeOper(typeOper); } catch (Exception e) { this.errorMsg = this.methodKey + " - " + typeOper + " is not valid."; } } - public void setValidContentFormatName (JsonObject rpcRequest){ + + public void setValidContentFormatName(JsonObject rpcRequest) { try { if (ContentFormat.fromName(rpcRequest.get(this.contentFormatNameKey).getAsString()) != null) { this.contentFormatName = rpcRequest.get(this.contentFormatNameKey).getAsString(); - } - else { + } else { this.errorMsg = this.contentFormatNameKey + " - " + rpcRequest.get(this.contentFormatNameKey).getAsString() + " is not valid."; } } catch (Exception e) { @@ -74,14 +74,14 @@ public class Lwm2mClientRpcRequest { } } - public void setValidTargetIdVerKey (JsonObject rpcRequest, Registration registration){ + public void setValidTargetIdVerKey(JsonObject rpcRequest, Registration registration) { if (rpcRequest.has(this.targetIdVerKey)) { String targetIdVerStr = rpcRequest.get(targetIdVerKey).getAsString(); // targetIdVer without ver - ok try { // targetIdVer with/without ver - ok this.targetIdVer = validPathIdVer(targetIdVerStr, registration); - if (this.targetIdVer != null){ + if (this.targetIdVer != null) { this.infoMsg = String.format("Changed by: pathIdVer - %s", this.targetIdVer); } } catch (Exception e) { @@ -97,11 +97,9 @@ public class Lwm2mClientRpcRequest { payloadResp.addProperty(this.resultKey, this.responseCode); if (this.errorMsg != null) { payloadResp.addProperty(this.errorKey, this.errorMsg); - } - else if (this.valueMsg != null) { + } else if (this.valueMsg != null) { payloadResp.addProperty(this.valueKey, this.valueMsg); - } - else if (this.infoMsg != null) { + } else if (this.infoMsg != null) { payloadResp.addProperty(this.infoKey, this.infoMsg); } return TransportProtos.ToDeviceRpcResponseMsg.newBuilder() diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java index 5d8fedc9c7..6d2d3847fc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ModelObject.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.Data; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.node.LwM2mObjectInstance; + import java.util.Map; @Data diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java index bac4554b04..d947e22133 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisRegistrationStore.java @@ -18,12 +18,12 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.californium.core.coap.Token; import org.eclipse.californium.core.observe.ObservationStoreException; import org.eclipse.californium.elements.EndpointContext; -import org.eclipse.leshan.core.observation.Observation; -import org.eclipse.leshan.core.util.NamedThreadFactory; -import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.core.Destroyable; import org.eclipse.leshan.core.Startable; import org.eclipse.leshan.core.Stoppable; +import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.util.NamedThreadFactory; +import org.eclipse.leshan.core.util.Validate; import org.eclipse.leshan.server.californium.observation.ObserveUtil; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.redis.JedisLock; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java index fa0cf67694..47b13bb72b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisSecurityStore.java @@ -21,7 +21,6 @@ import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; import org.eclipse.leshan.server.security.SecurityStoreListener; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.ScanParams; import redis.clients.jedis.ScanResult; From 59606f8330ce2c056b88c8eac94cefab03a764c8 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 4 May 2021 19:44:15 +0300 Subject: [PATCH 122/127] Add support for dashboboards in mobile app. Introduce widget mobile actions. --- .../controller/DashboardController.java | 47 +++ .../src/app/core/services/mobile.service.ts | 70 ++++ .../dashboard-page.component.html | 6 +- .../dashboard-page.component.scss | 9 + .../dashboard-page.component.ts | 23 +- .../dashboard-toolbar.component.scss | 36 ++ .../default-state-controller.component.ts | 5 + .../entity-state-controller.component.ts | 3 + .../home/components/home-components.module.ts | 3 + .../mobile-action-editor.component.html | 100 ++++++ .../action/mobile-action-editor.component.ts | 253 ++++++++++++++ .../action/mobile-action-editor.models.ts | 317 ++++++++++++++++++ .../widget-action-dialog.component.html | 5 + .../action/widget-action-dialog.component.ts | 30 +- .../components/widget/widget.component.ts | 182 +++++++++- .../home/models/widget-component.models.ts | 5 +- ui-ngx/src/app/shared/models/widget.models.ts | 97 +++++- .../assets/locale/locale.constant-en_US.json | 15 +- 18 files changed, 1186 insertions(+), 20 deletions(-) create mode 100644 ui-ngx/src/app/core/services/mobile.service.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.html create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.models.ts diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java index 8616da8714..570f925c45 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java @@ -533,6 +533,37 @@ public class DashboardController extends BaseController { } } + @PreAuthorize("isAuthenticated()") + @RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET) + @ResponseBody + public HomeDashboardInfo getHomeDashboardInfo() throws ThingsboardException { + try { + SecurityUser securityUser = getCurrentUser(); + if (securityUser.isSystemAdmin()) { + return null; + } + User user = userService.findUserById(securityUser.getTenantId(), securityUser.getId()); + JsonNode additionalInfo = user.getAdditionalInfo(); + HomeDashboardInfo homeDashboardInfo; + homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); + if (homeDashboardInfo == null) { + if (securityUser.isCustomerUser()) { + Customer customer = customerService.findCustomerById(securityUser.getTenantId(), securityUser.getCustomerId()); + additionalInfo = customer.getAdditionalInfo(); + homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); + } + if (homeDashboardInfo == null) { + Tenant tenant = tenantService.findTenantById(securityUser.getTenantId()); + additionalInfo = tenant.getAdditionalInfo(); + homeDashboardInfo = extractHomeDashboardInfoFromAdditionalInfo(additionalInfo); + } + } + return homeDashboardInfo; + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) @ResponseBody @@ -582,6 +613,22 @@ public class DashboardController extends BaseController { } } + private HomeDashboardInfo extractHomeDashboardInfoFromAdditionalInfo(JsonNode additionalInfo) { + try { + if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { + String strDashboardId = additionalInfo.get(HOME_DASHBOARD_ID).asText(); + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); + checkDashboardId(dashboardId, Operation.READ); + boolean hideDashboardToolbar = true; + if (additionalInfo.has(HOME_DASHBOARD_HIDE_TOOLBAR)) { + hideDashboardToolbar = additionalInfo.get(HOME_DASHBOARD_HIDE_TOOLBAR).asBoolean(); + } + return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); + } + } catch (Exception e) {} + return null; + } + private HomeDashboard extractHomeDashboardFromAdditionalInfo(JsonNode additionalInfo) { try { if (additionalInfo != null && additionalInfo.has(HOME_DASHBOARD_ID) && !additionalInfo.get(HOME_DASHBOARD_ID).isNull()) { diff --git a/ui-ngx/src/app/core/services/mobile.service.ts b/ui-ngx/src/app/core/services/mobile.service.ts new file mode 100644 index 0000000000..b6774f37d0 --- /dev/null +++ b/ui-ngx/src/app/core/services/mobile.service.ts @@ -0,0 +1,70 @@ +/// +/// Copyright © 2016-2021 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 { Inject, Injectable } from '@angular/core'; +import { WINDOW } from '@core/services/window.service'; +import { isDefined } from '@core/utils'; +import { MobileActionResult, WidgetMobileActionResult, WidgetMobileActionType } from '@shared/models/widget.models'; +import { from, of } from 'rxjs'; +import { Observable } from 'rxjs/internal/Observable'; +import { catchError } from 'rxjs/operators'; + +const dashboardStateNameHandler = 'tbMobileDashboardStateNameHandler'; +const mobileHandler = 'tbMobileHandler'; + +// @dynamic +@Injectable({ + providedIn: 'root' +}) +export class MobileService { + + private readonly mobileApp; + private readonly mobileChannel; + + constructor(@Inject(WINDOW) private window: Window) { + const w = (this.window as any); + this.mobileChannel = w.flutter_inappwebview; + this.mobileApp = isDefined(this.mobileChannel); + } + + public isMobileApp(): boolean { + return this.mobileApp; + } + + public handleDashboardStateName(name: string) { + if (this.mobileApp) { + this.mobileChannel.callHandler(dashboardStateNameHandler, name); + } + } + + public handleWidgetMobileAction(type: WidgetMobileActionType, ...args: any[]): + Observable> { + if (this.mobileApp) { + return from( + this.mobileChannel.callHandler(mobileHandler, type, ...args) as Promise>).pipe( + catchError((err: Error) => { + return of({ + hasError: true, + error: err?.message ? err.message : `Failed to execute mobile action ${type}` + } as WidgetMobileActionResult); + }) + ); + } else { + return of(null); + } + } + +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html index 3f03ea4fff..23555da05d 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.html @@ -15,7 +15,7 @@ limitations under the License. --> -
-
@@ -49,7 +49,7 @@ div { + height: $mobile-app-toolbar-height; + } + } + } + } + } + } + } + } + } +} + tb-dashboard-toolbar { mat-fab-toolbar { mat-fab-trigger { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts index 6d2e756489..27e613a64e 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts @@ -26,6 +26,7 @@ import { UtilsService } from '@core/services/utils.service'; import { base64toObj, objToBase64URI } from '@app/core/utils'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { EntityService } from '@core/http/entity.service'; +import { MobileService } from '@core/services/mobile.service'; @Component({ selector: 'tb-default-state-controller', @@ -40,6 +41,7 @@ export class DefaultStateControllerComponent extends StateControllerComponent im protected statesControllerService: StatesControllerService, private utils: UtilsService, private entityService: EntityService, + private mobileService: MobileService, private dashboardUtils: DashboardUtilsService) { super(router, route, ngZone, statesControllerService); } @@ -229,6 +231,9 @@ export class DefaultStateControllerComponent extends StateControllerComponent im private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) { if (this.dashboardCtrl.dashboardCtx.state !== stateId) { this.dashboardCtrl.openDashboardState(stateId, openRightLayout); + if (stateId && this.statesValue[stateId]) { + this.mobileService.handleDashboardStateName(this.getStateName(stateId, this.statesValue[stateId])); + } if (update) { this.updateLocation(); } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts index 39b340901d..cd5628dc06 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/entity-state-controller.component.ts @@ -28,6 +28,7 @@ import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { EntityService } from '@core/http/entity.service'; import { EntityType } from '@shared/models/entity-type.models'; import { map, tap } from 'rxjs/operators'; +import { MobileService } from '@core/services/mobile.service'; @Component({ selector: 'tb-entity-state-controller', @@ -44,6 +45,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp protected statesControllerService: StatesControllerService, private utils: UtilsService, private entityService: EntityService, + private mobileService: MobileService, private dashboardUtils: DashboardUtilsService) { super(router, route, ngZone, statesControllerService); } @@ -270,6 +272,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp private gotoState(stateId: string, update: boolean, openRightLayout?: boolean) { this.dashboardCtrl.openDashboardState(stateId, openRightLayout); + this.mobileService.handleDashboardStateName(this.getStateName(this.stateObject.length - 1)); if (update) { this.updateLocation(); } 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 f2208757d4..b39a566149 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 @@ -55,6 +55,7 @@ import { ManageWidgetActionsComponent } from '@home/components/widget/action/man import { WidgetActionDialogComponent } from '@home/components/widget/action/widget-action-dialog.component'; import { CustomActionPrettyResourcesTabsComponent } from '@home/components/widget/action/custom-action-pretty-resources-tabs.component'; import { CustomActionPrettyEditorComponent } from '@home/components/widget/action/custom-action-pretty-editor.component'; +import { MobileActionEditorComponent } from '@home/components/widget/action/mobile-action-editor.component'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; import { CustomDialogContainerComponent } from '@home/components/widget/dialog/custom-dialog-container.component'; import { ImportExportService } from '@home/components/import-export/import-export.service'; @@ -183,6 +184,7 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag WidgetActionDialogComponent, CustomActionPrettyResourcesTabsComponent, CustomActionPrettyEditorComponent, + MobileActionEditorComponent, CustomDialogContainerComponent, ImportDialogComponent, ImportDialogCsvComponent, @@ -296,6 +298,7 @@ import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-pag WidgetActionDialogComponent, CustomActionPrettyResourcesTabsComponent, CustomActionPrettyEditorComponent, + MobileActionEditorComponent, CustomDialogContainerComponent, ImportDialogComponent, ImportDialogCsvComponent, diff --git a/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.html b/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.html new file mode 100644 index 0000000000..cdb4615068 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.html @@ -0,0 +1,100 @@ + +
+ + widget-action.mobile.action-type + + + {{ mobileActionTypeTranslations.get(mobileActionType[actionType]) | translate }} + + + + {{ 'widget-action.mobile.action-type-required' | translate }} + + +
+ + + + + + + + + + + + + + + + + + +
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.ts new file mode 100644 index 0000000000..966268faa3 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.component.ts @@ -0,0 +1,253 @@ +/// +/// Copyright © 2016-2021 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, QueryList, ViewChild, ViewChildren } 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 { deepClone } from '@core/utils'; +import { + WidgetMobileActionDescriptor, + WidgetMobileActionType, widgetMobileActionTypeTranslationMap +} from '@shared/models/widget.models'; +import { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; +import { JsFuncComponent } from '@shared/components/js-func.component'; +import { + getDefaultGetLocationFunction, getDefaultGetPhoneNumberFunction, + getDefaultHandleEmptyResultFunction, + getDefaultHandleErrorFunction, + getDefaultProcessImageFunction, + getDefaultProcessLaunchResultFunction, getDefaultProcessLocationFunction, + getDefaultProcessQrCodeFunction +} from '@home/components/widget/action/mobile-action-editor.models'; + +@Component({ + selector: 'tb-mobile-action-editor', + templateUrl: './mobile-action-editor.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => MobileActionEditorComponent), + multi: true + }] +}) +export class MobileActionEditorComponent implements ControlValueAccessor, OnInit { + + @ViewChildren(JsFuncComponent) jsFuncComponents: QueryList; + + mobileActionTypes = Object.keys(WidgetMobileActionType); + mobileActionTypeTranslations = widgetMobileActionTypeTranslationMap; + mobileActionType = WidgetMobileActionType; + + customActionEditorCompleter = CustomActionEditorCompleter; + + mobileActionFormGroup: FormGroup; + mobileActionTypeFormGroup: 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.mobileActionFormGroup = this.fb.group({ + type: [null, Validators.required], + handleEmptyResultFunction: [null], + handleErrorFunction: [null] + }); + this.mobileActionFormGroup.get('type').valueChanges.subscribe((type: WidgetMobileActionType) => { + let action: WidgetMobileActionDescriptor = this.mobileActionFormGroup.value; + if (this.mobileActionTypeFormGroup) { + action = {...action, ...this.mobileActionTypeFormGroup.value}; + } + this.updateMobileActionType(type, action); + }); + this.mobileActionFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.mobileActionFormGroup.disable({emitEvent: false}); + if (this.mobileActionTypeFormGroup) { + this.mobileActionTypeFormGroup.disable({emitEvent: false}); + } + } else { + this.mobileActionFormGroup.enable({emitEvent: false}); + if (this.mobileActionTypeFormGroup) { + this.mobileActionTypeFormGroup.enable({emitEvent: false}); + } + } + } + + writeValue(value: WidgetMobileActionDescriptor | null): void { + this.mobileActionFormGroup.patchValue({type: value?.type, + handleEmptyResultFunction: value?.handleEmptyResultFunction, + handleErrorFunction: value?.handleErrorFunction}, {emitEvent: false}); + this.updateMobileActionType(value?.type, value); + } + + private updateModel() { + let descriptor: WidgetMobileActionDescriptor = null; + if (this.mobileActionFormGroup.valid && this.mobileActionTypeFormGroup.valid) { + descriptor = { ...this.mobileActionFormGroup.getRawValue(), ...this.mobileActionTypeFormGroup.getRawValue() }; + } + this.propagateChange(descriptor); + } + + private updateMobileActionType(type?: WidgetMobileActionType, action?: WidgetMobileActionDescriptor) { + const prevType = action?.type; + const targetType = type || prevType; + const changed = prevType !== type; + if (changed && targetType) { + let handleEmptyResultFunction = action?.handleEmptyResultFunction; + const defaultHandleEmptyResultFunction = getDefaultHandleEmptyResultFunction(targetType); + if (defaultHandleEmptyResultFunction !== handleEmptyResultFunction) { + handleEmptyResultFunction = getDefaultHandleEmptyResultFunction(type); + this.mobileActionFormGroup.patchValue({handleEmptyResultFunction}, {emitEvent: false}); + } + let handleErrorFunction = action?.handleErrorFunction; + const defaultHandleErrorFunction = getDefaultHandleErrorFunction(targetType); + if (defaultHandleErrorFunction !== handleErrorFunction) { + handleErrorFunction = getDefaultHandleErrorFunction(type); + this.mobileActionFormGroup.patchValue({handleErrorFunction}, {emitEvent: false}); + } + } + this.mobileActionTypeFormGroup = this.fb.group({}); + if (type) { + switch (type) { + case WidgetMobileActionType.takePictureFromGallery: + case WidgetMobileActionType.takePhoto: + case WidgetMobileActionType.takeScreenshot: + let processImageFunction = action?.processImageFunction; + if (changed) { + const defaultProcessImageFunction = getDefaultProcessImageFunction(targetType); + if (defaultProcessImageFunction !== processImageFunction) { + processImageFunction = getDefaultProcessImageFunction(type); + } + } + this.mobileActionTypeFormGroup.addControl( + 'processImageFunction', + this.fb.control(processImageFunction, []) + ); + break; + case WidgetMobileActionType.mapDirection: + case WidgetMobileActionType.mapLocation: + let getLocationFunction = action?.getLocationFunction; + let processLaunchResultFunction = action?.processLaunchResultFunction; + if (changed) { + const defaultGetLocationFunction = getDefaultGetLocationFunction(); + if (defaultGetLocationFunction !== getLocationFunction) { + getLocationFunction = defaultGetLocationFunction; + } + const defaultProcessLaunchResultFunction = getDefaultProcessLaunchResultFunction(targetType); + if (defaultProcessLaunchResultFunction !== processLaunchResultFunction) { + processLaunchResultFunction = getDefaultProcessLaunchResultFunction(type); + } + } + this.mobileActionTypeFormGroup.addControl( + 'getLocationFunction', + this.fb.control(getLocationFunction, [Validators.required]) + ); + this.mobileActionTypeFormGroup.addControl( + 'processLaunchResultFunction', + this.fb.control(processLaunchResultFunction, []) + ); + break; + case WidgetMobileActionType.scanQrCode: + let processQrCodeFunction = action?.processQrCodeFunction; + if (changed) { + const defaultProcessQrCodeFunction = getDefaultProcessQrCodeFunction(); + if (defaultProcessQrCodeFunction !== processQrCodeFunction) { + processQrCodeFunction = defaultProcessQrCodeFunction; + } + } + this.mobileActionTypeFormGroup.addControl( + 'processQrCodeFunction', + this.fb.control(processQrCodeFunction, []) + ); + break; + case WidgetMobileActionType.makePhoneCall: + let getPhoneNumberFunction = action?.getPhoneNumberFunction; + processLaunchResultFunction = action?.processLaunchResultFunction; + if (changed) { + const defaultGetPhoneNumberFunction = getDefaultGetPhoneNumberFunction(); + if (defaultGetPhoneNumberFunction !== getPhoneNumberFunction) { + getPhoneNumberFunction = defaultGetPhoneNumberFunction; + } + const defaultProcessLaunchResultFunction = getDefaultProcessLaunchResultFunction(targetType); + if (defaultProcessLaunchResultFunction !== processLaunchResultFunction) { + processLaunchResultFunction = getDefaultProcessLaunchResultFunction(type); + } + } + this.mobileActionTypeFormGroup.addControl( + 'getPhoneNumberFunction', + this.fb.control(getPhoneNumberFunction, [Validators.required]) + ); + this.mobileActionTypeFormGroup.addControl( + 'processLaunchResultFunction', + this.fb.control(processLaunchResultFunction, []) + ); + break; + case WidgetMobileActionType.getLocation: + let processLocationFunction = action?.processLocationFunction; + if (changed) { + const defaultProcessLocationFunction = getDefaultProcessLocationFunction(); + if (defaultProcessLocationFunction !== processLocationFunction) { + processLocationFunction = defaultProcessLocationFunction; + } + } + this.mobileActionTypeFormGroup.addControl( + 'processLocationFunction', + this.fb.control(processLocationFunction, [Validators.required]) + ); + break; + } + } + this.mobileActionTypeFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + public validateOnSubmit() { + for (const jsFuncComponent of this.jsFuncComponents.toArray()) { + jsFuncComponent.validateOnSubmit(); + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.models.ts b/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.models.ts new file mode 100644 index 0000000000..ea32b812be --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/widget/action/mobile-action-editor.models.ts @@ -0,0 +1,317 @@ +/// +/// Copyright © 2016-2021 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 { WidgetMobileActionType } from '@shared/models/widget.models'; + +const processImageFunctionTemplate = + '// Function body to process image obtained as a result of mobile action (take photo, take image from gallery, etc.). \n' + + '// - imageUrl - image URL in base64 data format\n\n' + + 'showImageDialog(\'--TITLE--\', imageUrl);\n' + + '//saveEntityImageAttribute(\'image\', imageUrl);\n' + + '\n' + + 'function showImageDialog(title, imageUrl) {\n' + + ' setTimeout(function() {\n' + + ' widgetContext.customDialog.customDialog(imageDialogTemplate, ImageDialogController, {imageUrl: imageUrl, title: title}).subscribe();\n' + + ' }, 100);\n' + + '}\n' + + '\n' + + 'function saveEntityImageAttribute(attributeName, imageUrl) {\n' + + ' if (entityId) {\n' + + ' let attributes = [{\n' + + ' key: attributeName, value: imageUrl\n' + + ' }];\n' + + ' widgetContext.attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributes).subscribe(\n' + + ' function() {\n' + + ' widgetContext.showSuccessToast(\'Image attribute saved!\');\n' + + ' },\n' + + ' function(error) {\n' + + ' widgetContext.dialogs.alert(\'Image attribute save failed\', JSON.stringify(error));\n' + + ' }\n' + + ' );\n' + + ' }\n' + + '}\n' + + '\n' + + 'var\n' + + ' imageDialogTemplate =\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'\' +\n' + + ' \'

{{title}}

\' +\n' + + ' \'\' +\n' + + ' \'\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'\' +\n' + + ' \'\' +\n' + + ' \'
\' +\n' + + ' \'
\' +\n' + + ' \'
\';\n' + + '\n' + + 'function ImageDialogController(instance) {\n' + + ' let vm = instance;\n' + + ' vm.title = vm.data.title;\n' + + ' vm.imageUrl = vm.data.imageUrl;\n' + + ' vm.close = function ()\n' + + ' {\n' + + ' vm.dialogRef.close(null);\n' + + ' }\n' + + '}\n'; + +const processLaunchResultFunctionTemplate = + '// Optional function body to process result of attempt to launch external mobile application (for ex. map application or phone call application). \n' + + '// - launched - boolean value indicating if the external application was successfully launched.\n\n' + + 'showLaunchStatusDialog(\'--TITLE--\', launched);\n' + + '\n' + + 'function showLaunchStatusDialog(title, status) {\n' + + ' setTimeout(function() {\n' + + ' widgetContext.dialogs.alert(title, status ? \'Successfully launched\' : \'Failed to launch\').subscribe();\n' + + ' }, 100);\n' + + '}\n'; + +const processQrCodeFunction = + '// Function body to process result of QR code scanning. \n' + + '// - code - scanned QR code\n' + + '// - format - scanned QR code format\n\n' + + 'showQrCodeDialog(\'QR Code\', code, format);\n' + + '\n' + + 'function showQrCodeDialog(title, code, format) {\n' + + ' setTimeout(function() {\n' + + ' widgetContext.dialogs.alert(title, \'Code: [\'+code+\']
Format: \' + format).subscribe();\n' + + ' }, 100);\n' + + '}\n'; + +const processLocationFunction = + '// Function body to process current location of the phone. \n' + + '// - latitude - phone location latitude\n' + + '// - longitude - phone location longitude\n\n' + + 'showLocationDialog(\'Location\', latitude, longitude);\n' + + '// saveEntityLocationAttributes(\'latitude\', \'longitude\', latitude, longitude);\n' + + '\n' + + 'function showImageDialog(title, imageUrl) {\n' + + ' setTimeout(function() {\n' + + ' widgetContext.customDialog.customDialog(imageDialogTemplate, ImageDialogController, {imageUrl: imageUrl, title: title}).subscribe();\n' + + ' }, 100);\n' + + '}\n' + + '\n' + + 'function saveEntityLocationAttributes(latitudeAttributeName, longitudeAttributeName, latitude, longitude) {\n' + + ' if (entityId) {\n' + + ' let attributes = [\n' + + ' { key: latitudeAttributeName, value: latitude },\n' + + ' { key: longitudeAttributeName, value: longitude }\n' + + ' ];\n' + + ' widgetContext.attributeService.saveEntityAttributes(entityId, "SERVER_SCOPE", attributes).subscribe(\n' + + ' function() {\n' + + ' widgetContext.showSuccessToast(\'Location attributes saved!\');\n' + + ' },\n' + + ' function(error) {\n' + + ' widgetContext.dialogs.alert(\'Location attributes save failed\', JSON.stringify(error));\n' + + ' }\n' + + ' );\n' + + ' }\n' + + '}\n' + + '\n' + + '\n' + + 'function showLocationDialog(title, latitude, longitude) {\n' + + ' setTimeout(function() {\n' + + ' widgetContext.dialogs.alert(title, \'Latitude: \'+latitude+\'
Longitude: \' + longitude).subscribe();\n' + + ' }, 100);\n' + + '}'; + +const handleEmptyResultFunctionTemplate = + '// Optional function body to handle empty result. \n' + + '// Usually this happens when user cancels the action (for ex. by pressing phone back button). \n\n' + + 'showEmptyResultDialog(\'--MESSAGE--\');\n' + + '\n' + + 'function showEmptyResultDialog(message) {\n' + + ' setTimeout(function() {\n' + + ' widgetContext.dialogs.alert(\'Empty result\', message).subscribe();\n' + + ' }, 100);\n' + + '}\n'; + +const handleErrorFunctionTemplate = + '// Optional function body to handle error occurred while mobile action execution \n' + + '// - error - Error message\n\n' + + 'showErrorDialog(\'--TITLE--\', error);\n' + + '\n' + + 'function showErrorDialog(title, error) {\n' + + ' setTimeout(function() {\n' + + ' widgetContext.dialogs.alert(title, error).subscribe();\n' + + ' }, 100);\n' + + '}\n'; + +const getLocationFunctionTemplate = + '// Function body that should return location as array of two numbers (latitude, longitude) for further processing by mobile action.\n' + + '// Usually location can be obtained from entity attributes/telemetry. \n\n' + + 'return getLocationFromEntityAttributes();\n' + + '//return [30, 30]; // TEST LOCATION\n' + + '\n' + + '\n' + + 'function getLocationFromEntityAttributes() {\n' + + ' if (entityId) {\n' + + ' return widgetContext.attributeService.getEntityAttributes(entityId, \'SERVER_SCOPE\', [\'latitude\', \'longitude\']).pipe(widgetContext.rxjs.map(function(attributeData) {\n' + + ' var res = [0,0];\n' + + ' if (attributeData && attributeData.length === 2) {\n' + + ' res[0] = attributeData.filter(function (data) { return data.key === \'latitude\'})[0].value;\n' + + ' res[1] = attributeData.filter(function (data) { return data.key === \'longitude\'})[0].value;\n' + + ' }\n' + + ' return res;\n' + + ' }));\n' + + ' } else {\n' + + ' return [0,0];\n' + + ' }\n' + + '}\n'; + +const getPhoneNumberFunctionTemplate = + '// Function body that should return phone number for further processing by mobile action.\n' + + '// Usually phone number can be obtained from entity attributes/telemetry. \n\n' + + 'return getPhoneNumberFromEntityAttributes();\n' + + '//return 123456789; // TEST PHONE NUMBER\n' + + '\n' + + '\n' + + 'function getPhoneNumberFromEntityAttributes() {\n' + + ' if (entityId) {\n' + + ' return widgetContext.attributeService.getEntityAttributes(entityId, \'SERVER_SCOPE\', [\'phone\']).pipe(widgetContext.rxjs.map(function(attributeData) {\n' + + ' var res = 0;\n' + + ' if (attributeData && attributeData.length === 1) {\n' + + ' res = attributeData[0].value;\n' + + ' }\n' + + ' return res;\n' + + ' }));\n' + + ' } else {\n' + + ' return 0;\n' + + ' }\n' + + '}\n'; + +export function getDefaultProcessImageFunction(type: WidgetMobileActionType): string { + let title; + switch (type) { + case WidgetMobileActionType.takePictureFromGallery: + title = 'Gallery picture'; + break; + case WidgetMobileActionType.takePhoto: + title = 'Photo'; + break; + case WidgetMobileActionType.takeScreenshot: + title = 'Screenshot'; + break; + } + return processImageFunctionTemplate.replace('--TITLE--', title); +} + +export function getDefaultProcessLaunchResultFunction(type: WidgetMobileActionType): string { + let title; + switch (type) { + case WidgetMobileActionType.mapLocation: + title = 'Map location'; + break; + case WidgetMobileActionType.mapDirection: + title = 'Map direction'; + break; + case WidgetMobileActionType.makePhoneCall: + title = 'Phone call'; + break; + } + return processLaunchResultFunctionTemplate.replace('--TITLE--', title); +} + +export function getDefaultProcessQrCodeFunction() { + return processQrCodeFunction; +} + +export function getDefaultProcessLocationFunction() { + return processLocationFunction; +} + +export function getDefaultGetLocationFunction() { + return getLocationFunctionTemplate; +} + +export function getDefaultGetPhoneNumberFunction() { + return getPhoneNumberFunctionTemplate; +} + +export function getDefaultHandleEmptyResultFunction(type: WidgetMobileActionType): string { + let message = 'Mobile action was cancelled!'; + switch (type) { + case WidgetMobileActionType.takePictureFromGallery: + message = 'Take picture from gallery action was cancelled!'; + break; + case WidgetMobileActionType.takePhoto: + message = 'Take photo action was cancelled!'; + break; + case WidgetMobileActionType.mapDirection: + message = 'Open map directions was not invoked!'; + break; + case WidgetMobileActionType.mapLocation: + message = 'Open location on map was not invoked!'; + break; + case WidgetMobileActionType.scanQrCode: + message = 'Scan QR code action was canceled!'; + break; + case WidgetMobileActionType.makePhoneCall: + message = 'Phone call was not invoked!'; + break; + case WidgetMobileActionType.getLocation: + message = 'Get location action was canceled!'; + break; + case WidgetMobileActionType.takeScreenshot: + message = 'Take screenshot action was cancelled!'; + break; + } + return handleEmptyResultFunctionTemplate.replace('--MESSAGE--', message); +} + +export function getDefaultHandleErrorFunction(type: WidgetMobileActionType): string { + let title = 'Mobile action failed'; + switch (type) { + case WidgetMobileActionType.takePictureFromGallery: + title = 'Failed to take picture from gallery'; + break; + case WidgetMobileActionType.takePhoto: + title = 'Failed to take photo'; + break; + case WidgetMobileActionType.mapDirection: + title = 'Failed to open map directions'; + break; + case WidgetMobileActionType.mapLocation: + title = 'Failed to open map location'; + break; + case WidgetMobileActionType.scanQrCode: + title = 'Failed to scan QR code'; + break; + case WidgetMobileActionType.makePhoneCall: + title = 'Failed to make phone call'; + break; + case WidgetMobileActionType.getLocation: + title = 'Failed to get phone location'; + break; + case WidgetMobileActionType.takeScreenshot: + title = 'Failed to take screenshot'; + break; + } + return handleErrorFunctionTemplate.replace('--TITLE--', title); +} diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html index 33678c88b3..44bfe44e1e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.html @@ -180,6 +180,11 @@ formControlName="customAction"> + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts index c2fc1e3cfe..0227ded562 100644 --- a/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/action/widget-action-dialog.component.ts @@ -45,6 +45,7 @@ import { Dashboard } from '@shared/models/dashboard.models'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { CustomActionEditorCompleter } from '@home/components/widget/action/custom-action.models'; import { isDefinedAndNotNull } from '@core/utils'; +import { MobileActionEditorComponent } from '@home/components/widget/action/mobile-action-editor.component'; export interface WidgetActionDialogData { isAdd: boolean; @@ -64,6 +65,8 @@ export class WidgetActionDialogComponent extends DialogComponent; + switch (type) { + case WidgetMobileActionType.takePictureFromGallery: + case WidgetMobileActionType.takePhoto: + case WidgetMobileActionType.scanQrCode: + case WidgetMobileActionType.getLocation: + case WidgetMobileActionType.takeScreenshot: + argsObservable = of([]); + break; + case WidgetMobileActionType.mapDirection: + case WidgetMobileActionType.mapLocation: + const getLocationFunctionString = mobileAction.getLocationFunction; + const getLocationFunction = new Function('$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', 'entityLabel', getLocationFunctionString); + const locationArgs = getLocationFunction($event, this.widgetContext, entityId, entityName, additionalParams, entityLabel); + if (locationArgs && locationArgs instanceof Observable) { + argsObservable = locationArgs; + } else { + argsObservable = of(locationArgs); + } + argsObservable = argsObservable.pipe(map(latLng => { + let valid = false; + if (Array.isArray(latLng) && latLng.length === 2) { + if (typeof latLng[0] === 'number' && typeof latLng[1] === 'number') { + valid = true; + } + } + if (valid) { + return latLng; + } else { + throw new Error('Location function did not return valid array of latitude/longitude!'); + } + })); + break; + case WidgetMobileActionType.makePhoneCall: + const getPhoneNumberFunctionString = mobileAction.getPhoneNumberFunction; + const getPhoneNumberFunction = new Function('$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', 'entityLabel', getPhoneNumberFunctionString); + const phoneNumberArg = getPhoneNumberFunction($event, this.widgetContext, entityId, entityName, additionalParams, entityLabel); + if (phoneNumberArg && phoneNumberArg instanceof Observable) { + argsObservable = phoneNumberArg.pipe(map(phoneNumber => [phoneNumber])); + } else { + argsObservable = of([phoneNumberArg]); + } + argsObservable = argsObservable.pipe(map(phoneNumberArr => { + let valid = false; + if (Array.isArray(phoneNumberArr) && phoneNumberArr.length === 1) { + if (phoneNumberArr[0] !== null) { + valid = true; + } + } + if (valid) { + return phoneNumberArr; + } else { + throw new Error('Phone number function did not return valid number!'); + } + })); + break; + } + argsObservable.subscribe((args) => { + this.mobileService.handleWidgetMobileAction(type, ...args).subscribe( + (result) => { + if (result) { + if (result.hasError) { + this.handleWidgetMobileActionError(result.error, $event, mobileAction, entityId, entityName, additionalParams, entityLabel); + } else if (result.hasResult) { + const actionResult = result.result; + switch (type) { + case WidgetMobileActionType.takePictureFromGallery: + case WidgetMobileActionType.takePhoto: + case WidgetMobileActionType.takeScreenshot: + const imageUrl = actionResult.imageUrl; + if (mobileAction.processImageFunction && mobileAction.processImageFunction.length) { + try { + const processImageFunction = new Function('imageUrl', '$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', 'entityLabel', mobileAction.processImageFunction); + processImageFunction(imageUrl, $event, this.widgetContext, entityId, entityName, additionalParams, entityLabel); + } catch (e) { + console.error(e); + } + } + break; + case WidgetMobileActionType.scanQrCode: + const code = actionResult.code; + const format = actionResult.format; + if (mobileAction.processQrCodeFunction && mobileAction.processQrCodeFunction.length) { + try { + const processQrCodeFunction = new Function('code', 'format', '$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', 'entityLabel', mobileAction.processQrCodeFunction); + processQrCodeFunction(code, format, $event, this.widgetContext, entityId, entityName, additionalParams, entityLabel); + } catch (e) { + console.error(e); + } + } + break; + case WidgetMobileActionType.getLocation: + const latitude = actionResult.latitude; + const longitude = actionResult.longitude; + if (mobileAction.processLocationFunction && mobileAction.processLocationFunction.length) { + try { + const processLocationFunction = new Function('latitude', 'longitude', '$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', 'entityLabel', mobileAction.processLocationFunction); + processLocationFunction(latitude, longitude, $event, this.widgetContext, + entityId, entityName, additionalParams, entityLabel); + } catch (e) { + console.error(e); + } + } + break; + case WidgetMobileActionType.mapDirection: + case WidgetMobileActionType.mapLocation: + case WidgetMobileActionType.makePhoneCall: + const launched = actionResult.launched; + if (mobileAction.processLaunchResultFunction && mobileAction.processLaunchResultFunction.length) { + try { + const processLaunchResultFunction = new Function('launched', '$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', 'entityLabel', mobileAction.processLaunchResultFunction); + processLaunchResultFunction(launched, $event, this.widgetContext, + entityId, entityName, additionalParams, entityLabel); + } catch (e) { + console.error(e); + } + } + break; + } + } else { + if (mobileAction.handleEmptyResultFunction && mobileAction.handleEmptyResultFunction.length) { + try { + const handleEmptyResultFunction = new Function('$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', 'entityLabel', mobileAction.handleEmptyResultFunction); + handleEmptyResultFunction($event, this.widgetContext, entityId, entityName, additionalParams, entityLabel); + } catch (e) { + console.error(e); + } + } + } + } + } + ); + }, + (err) => { + let errorMessage; + if (err && typeof err === 'string') { + errorMessage = err; + } else if (err && err.message) { + errorMessage = err.message; + } + errorMessage = `Failed to get mobile action arguments${errorMessage ? `: ${errorMessage}` : '!'}`; + this.handleWidgetMobileActionError(errorMessage, $event, mobileAction, entityId, entityName, additionalParams, entityLabel); + }); + } + + private handleWidgetMobileActionError(error: string, $event: Event, mobileAction: WidgetMobileActionDescriptor, + entityId?: EntityId, entityName?: string, additionalParams?: any, entityLabel?: string) { + if (mobileAction.handleErrorFunction && mobileAction.handleErrorFunction.length) { + try { + const handleErrorFunction = new Function('error', '$event', 'widgetContext', 'entityId', + 'entityName', 'additionalParams', 'entityLabel', mobileAction.handleErrorFunction); + handleErrorFunction(error, $event, this.widgetContext, entityId, entityName, additionalParams, entityLabel); + } catch (e) { + console.error(e); + } } } diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 74b6496c67..64fbe59efa 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -77,6 +77,7 @@ import { PageLink } from '@shared/models/page/page-link'; import { SortOrder } from '@shared/models/page/sort-order'; import { DomSanitizer } from '@angular/platform-browser'; import { Router } from '@angular/router'; +import { map, mergeMap } from 'rxjs/operators'; export interface IWidgetAction { name: string; @@ -238,7 +239,9 @@ export class WidgetContext { rxjs = { forkJoin, - of + of, + map, + mergeMap }; showSuccessToast(message: string, duration: number = 1000, diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 2d599bdc78..edf0eb803a 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -332,7 +332,19 @@ export enum WidgetActionType { updateDashboardState = 'updateDashboardState', openDashboard = 'openDashboard', custom = 'custom', - customPretty = 'customPretty' + customPretty = 'customPretty', + mobileAction = 'mobileAction' +} + +export enum WidgetMobileActionType { + takePictureFromGallery = 'takePictureFromGallery', + takePhoto = 'takePhoto', + mapDirection = 'mapDirection', + mapLocation = 'mapLocation', + scanQrCode = 'scanQrCode', + makePhoneCall = 'makePhoneCall', + getLocation = 'getLocation', + takeScreenshot = 'takeScreenshot' } export const widgetActionTypeTranslationMap = new Map( @@ -341,10 +353,90 @@ export const widgetActionTypeTranslationMap = new Map( [ WidgetActionType.updateDashboardState, 'widget-action.update-dashboard-state' ], [ WidgetActionType.openDashboard, 'widget-action.open-dashboard' ], [ WidgetActionType.custom, 'widget-action.custom' ], - [ WidgetActionType.customPretty, 'widget-action.custom-pretty' ] + [ WidgetActionType.customPretty, 'widget-action.custom-pretty' ], + [ WidgetActionType.mobileAction, 'widget-action.mobile-action' ] + ] +); + +export const widgetMobileActionTypeTranslationMap = new Map( + [ + [ WidgetMobileActionType.takePictureFromGallery, 'widget-action.mobile.take-picture-from-gallery' ], + [ WidgetMobileActionType.takePhoto, 'widget-action.mobile.take-photo' ], + [ WidgetMobileActionType.mapDirection, 'widget-action.mobile.map-direction' ], + [ WidgetMobileActionType.mapLocation, 'widget-action.mobile.map-location' ], + [ WidgetMobileActionType.scanQrCode, 'widget-action.mobile.scan-qr-code' ], + [ WidgetMobileActionType.makePhoneCall, 'widget-action.mobile.make-phone-call' ], + [ WidgetMobileActionType.getLocation, 'widget-action.mobile.get-location' ], + [ WidgetMobileActionType.takeScreenshot, 'widget-action.mobile.take-screenshot' ] ] ); +export interface MobileLaunchResult { + launched: boolean; +} + +export interface MobileImageResult { + imageUrl: string; +} + +export interface MobileQrCodeResult { + code: string; + format: string; +} + +export interface MobileLocationResult { + latitude: number; + longitude: number; +} + +export type MobileActionResult = MobileLaunchResult & + MobileImageResult & + MobileQrCodeResult & + MobileLocationResult; + +export interface WidgetMobileActionResult { + result?: T; + hasResult: boolean; + error?: string; + hasError: boolean; +} + +export interface ProcessImageDescriptor { + processImageFunction: string; +} + +export interface ProcessLaunchResultDescriptor { + processLaunchResultFunction?: string; +} + +export interface LaunchMapDescriptor extends ProcessLaunchResultDescriptor { + getLocationFunction: string; +} + +export interface ScanQrCodeDescriptor { + processQrCodeFunction: string; +} + +export interface MakePhoneCallDescriptor extends ProcessLaunchResultDescriptor { + getPhoneNumberFunction: string; +} + +export interface GetLocationDescriptor { + processLocationFunction: string; +} + +export type WidgetMobileActionDescriptors = ProcessImageDescriptor & + LaunchMapDescriptor & + ScanQrCodeDescriptor & + MakePhoneCallDescriptor & + GetLocationDescriptor; + +export interface WidgetMobileActionDescriptor extends WidgetMobileActionDescriptors { + type: WidgetMobileActionType; + handleErrorFunction?: string; + handleEmptyResultFunction?: string; +} + export interface CustomActionDescriptor { customFunction?: string; customResources?: Array; @@ -369,6 +461,7 @@ export interface WidgetActionDescriptor extends CustomActionDescriptor { dialogHeight?: number; setEntityId?: boolean; stateEntityParamName?: string; + mobileAction?: WidgetMobileActionDescriptor; } export interface WidgetComparisonSettings { 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 08335a0175..160c42ff0a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2704,6 +2704,7 @@ "open-dashboard": "Navigate to other dashboard", "custom": "Custom action", "custom-pretty": "Custom action (with HTML template)", + "mobile-action": "Mobile action", "target-dashboard-state": "Target dashboard state", "target-dashboard-state-required": "Target dashboard state is required", "set-entity-from-widget": "Set entity from widget", @@ -2715,7 +2716,19 @@ "dialog-width": "Dialog width in percents relative to viewport width", "dialog-height": "Dialog height in percents relative to viewport height", "dialog-size-range-error": "Dialog size percent value should be in a range from 1 to 100.", - "open-new-browser-tab": "Open in a new browser tab" + "open-new-browser-tab": "Open in a new browser tab", + "mobile": { + "action-type": "Mobile action type", + "action-type-required": "Mobile action type is required", + "take-picture-from-gallery": "Take picture from gallery", + "take-photo": "Take photo", + "map-direction": "Open map directions", + "map-location": "Open map location", + "scan-qr-code": "Scan QR Code", + "make-phone-call": "Make phone call", + "get-location": "Get phone location", + "take-screenshot": "Take screenshot" + } }, "widgets-bundle": { "current": "Current bundle", From bacf822f156cb526ee3cf76d22db1e945831e071 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 5 May 2021 00:39:35 +0300 Subject: [PATCH 123/127] Lwm2m: add Write_Update example #1 --- .../DefaultLwM2MTransportMsgHandler.java | 20 +++++--- .../lwm2m/server/LwM2mTransportRequest.java | 51 +++++++++++++------ .../server/LwM2mVersionedModelProvider.java | 2 - .../lwm2m/server/client/LwM2mClient.java | 12 +++-- .../lwm2m/utils/LwM2mValueConverterImpl.java | 8 ++- 5 files changed, 63 insertions(+), 30 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index 8e059e5a3b..5d724b940b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -33,7 +33,6 @@ import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.util.NamedThreadFactory; -import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -102,7 +101,6 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_REPLACE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.WRITE_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.SERVICE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertJsonArrayToSet; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId; @@ -490,15 +488,17 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler if (OBSERVE_READ_ALL != lwm2mClientRpcRequest.getTypeOper() && lwm2mClientRpcRequest.getTargetIdVer() == null) { lwm2mClientRpcRequest.setErrorMsg(lwm2mClientRpcRequest.targetIdVerKey + " and " + lwm2mClientRpcRequest.keyNameKey + " is null or bad format"); - } else if ((EXECUTE == lwm2mClientRpcRequest.getTypeOper() + } + /** + * EXECUTE && WRITE_REPLACE - only for Resource or ResourceInstance + */ + else if ((EXECUTE == lwm2mClientRpcRequest.getTypeOper() || WRITE_REPLACE == lwm2mClientRpcRequest.getTypeOper()) && lwm2mClientRpcRequest.getTargetIdVer() != null && !(new LwM2mPath(convertPathFromIdVerToObjectId(lwm2mClientRpcRequest.getTargetIdVer())).isResource() || new LwM2mPath(convertPathFromIdVerToObjectId(lwm2mClientRpcRequest.getTargetIdVer())).isResourceInstance())) { lwm2mClientRpcRequest.setErrorMsg("Invalid parameter " + lwm2mClientRpcRequest.targetIdVerKey + ". Only Resource or ResourceInstance can be this operation"); - } else if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()) { - lwm2mClientRpcRequest.setErrorMsg("Procedures In Development..."); } } else { lwm2mClientRpcRequest.setErrorMsg("Params of request is bad Json format."); @@ -930,7 +930,15 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param request - */ public void onWriteResponseOk(Registration registration, String path, WriteRequest request) { - this.updateResourcesValue(registration, ((LwM2mResource) request.getNode()), path); + if (request.getNode() instanceof LwM2mResource) { + this.updateResourcesValue(registration, ((LwM2mResource) request.getNode()), path); + } + else if (request.getNode() instanceof LwM2mObjectInstance) { + ((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> { + this.updateResourcesValue(registration, resource, path+ "/" + resId); + }); + } + } /** diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index 3557af6d40..e38823240b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -16,13 +16,13 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Response; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mPath; +import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.observation.Observation; @@ -46,10 +46,8 @@ import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.NamedThreadFactory; -import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @@ -59,6 +57,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; import java.util.Arrays; +import java.util.Collection; import java.util.Date; import java.util.Set; import java.util.concurrent.ExecutorService; @@ -111,7 +110,7 @@ public class LwM2mTransportRequest { * @param typeOper - * @param contentFormatName - */ - @SneakyThrows + public void sendAllRequest(Registration registration, String targetIdVer, LwM2mTypeOper typeOper, String contentFormatName, Object params, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { try { @@ -160,8 +159,7 @@ public class LwM2mTransportRequest { break; case WRITE_REPLACE: // Request to write a String Single-Instance Resource using the TLV content format. - resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config - .getModelProvider()); + resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); if (contentFormat.equals(ContentFormat.TLV)) { request = this.getWriteRequestSingleResource(null, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModel.type, @@ -175,15 +173,31 @@ public class LwM2mTransportRequest { } break; case WRITE_UPDATE: -// LwM2mNode node = null; -// if (resultIds.isObjectInstance()) { -// node = new LwM2mObjectInstance(resultIds.getObjectInstanceId(), lwM2MClient. -// getNewResourcesForInstance(targetIdVer, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getModelProvider(), -// this.converter)); -// request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, target, node); -// } else if (resultIds.getObjectId() >= 0) { -// request = new ObserveRequest(resultIds.getObjectId()); -// } + if (resultIds.isResource()) { + /** + * send request: path = '/3/0' node == wM2mObjectInstance + * with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} + **/ + Collection resources = lwM2MClient.getNewResourcesForInstance( + targetIdVer, params, + this.config.getModelProvider(), + this.converter); + request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), + resultIds.getObjectInstanceId(), resources); + } + + /** + * params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" + * + * int rscId = resultIds.getObjectInstanceId(); + */ + + else if (resultIds.isObjectInstance()) { + String content = (String) params; +// node = Gson.fromJson((content, LwM2mNode.class); + } else if (resultIds.getObjectId() >= 0) { + request = new ObserveRequest(resultIds.getObjectId()); + } break; case WRITE_ATTRIBUTES: request = createWriteAttributeRequest(target, params); @@ -236,7 +250,11 @@ public class LwM2mTransportRequest { String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR, typeOper.name(), e.getMessage()); serviceImpl.sendLogsToThingsboard(msg, registration.getId()); - throw new Exception(e); + try { + throw new Exception(e); + } catch (Exception exception) { + exception.printStackTrace(); + } } } @@ -248,6 +266,7 @@ public class LwM2mTransportRequest { @SuppressWarnings("unchecked") private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { + context.getServer().send(registration, request, timeoutInMs, (ResponseCallback) response -> { if (!lwM2MClient.isInit()) { lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java index bb27b66d69..654f96145a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mVersionedModelProvider.java @@ -117,9 +117,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { private ObjectModel getObjectModelDynamic(Integer objectId, String version) { String key = getKeyIdVer(objectId, version); - Optional tbResource = context.getTransportResourceCache().get(this.tenantId, LWM2M_MODEL, key); - return tbResource.map(resource -> helper.parseFromXmlToObjectModel( Base64.getDecoder().decode(resource.getData()), key + ".xml", diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index df212790da..3e56cbb374 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -26,8 +26,8 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.Collection; @@ -111,15 +111,17 @@ public class LwM2mClient implements Cloneable { .getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null; } - public Collection getNewResourcesForInstance(String pathRezIdVer, LwM2mModelProvider modelProvider, + public Collection getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, LwM2mValueConverterImpl converter) { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources; - resourceModels.forEach((k, resourceModel) -> { - resources.add(LwM2mSingleResource.newResource(k, converter.convertValue("0", ResourceModel.Type.STRING, resourceModel.type, pathIds), resourceModel.type)); - }); + resourceModels.forEach((resId, resourceModel) -> { + if (resId == pathIds.getResourceId()) { + resources.add(LwM2mSingleResource.newResource(resId, converter.convertValue(params, ResourceModel.Type.STRING, resourceModel.type, pathIds), resourceModel.type)); + + }}); return resources; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index dce34c9cf6..3395fd7090 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -60,11 +60,14 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { case INTEGER: switch (currentType) { case FLOAT: - log.debug("Trying to convert float value [{}] to integer", value); + log.debug("Trying to convert float value [{}] to Integer", value); Long longValue = ((Double) value).longValue(); if ((double) value == longValue.doubleValue()) { return longValue; } + case STRING: + log.debug("Trying to convert String value [{}] to Integer", value); + return Long.parseLong((String) value); default: break; } @@ -77,6 +80,9 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { if ((long) value == floatValue.longValue()) { return floatValue; } + case STRING: + log.debug("Trying to convert String value [{}] to Float", value); + return Float.valueOf((String) value); default: break; } From 72382a52e3e4b7d1b550c0d7fdb03a027c77c5a4 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 5 May 2021 12:48:49 +0300 Subject: [PATCH 124/127] LwM2M Server refactoring --- .../secure/LwM2MBootstrapSecurityStore.java | 10 +- ...tyStore.java => EndpointSecurityInfo.java} | 2 +- ...LwM2mCredentialsSecurityInfoValidator.java | 41 ++- .../DefaultLwM2MTransportMsgHandler.java | 281 ++++++------------ .../lwm2m/server/LwM2mTransportRequest.java | 2 +- .../lwm2m/server/LwM2mTransportUtil.java | 3 +- .../lwm2m/server/client/LwM2mClient.java | 98 +++++- .../server/client/LwM2mClientContext.java | 23 +- .../server/client/LwM2mClientContextImpl.java | 168 ++++++----- .../server/store/TbLwM2mSecurityStore.java | 94 ++++++ .../store/TbLwM2mStoreConfiguration.java | 128 -------- .../server/store/TbLwM2mStoreFactory.java | 59 ++++ 12 files changed, 467 insertions(+), 442 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/{ReadResultSecurityStore.java => EndpointSecurityInfo.java} (97%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreConfiguration.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java index d1e136d6f3..68c1ff405d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapSecurityStore.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; -import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; +import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; @@ -72,7 +72,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); + EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); @@ -96,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); + EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); @@ -113,7 +113,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { return null; } - private void setBootstrapConfigScurityInfo(ReadResultSecurityStore store) { + private void setBootstrapConfigScurityInfo(EndpointSecurityInfo store) { /* BootstrapConfig */ LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); if (lwM2MBootstrapConfig != null) { @@ -150,7 +150,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } } - private LwM2MBootstrapConfig getParametersBootstrap(ReadResultSecurityStore store) { + private LwM2MBootstrapConfig getParametersBootstrap(EndpointSecurityInfo store) { try { JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential(); if (bootstrapJsonCredential != null) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java similarity index 97% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java index 30f15116af..851934a806 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/ReadResultSecurityStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java @@ -25,7 +25,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCreden import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE; @Data -public class ReadResultSecurityStore { +public class EndpointSecurityInfo { private ValidateDeviceCredentialsResponseMsg msg; private SecurityInfo securityInfo; private int securityMode = DEFAULT_MODE.code; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java index 7154a234a4..0263e72fb2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java @@ -52,15 +52,10 @@ public class LwM2mCredentialsSecurityInfoValidator { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - /** - * Request to thingsboard Response from thingsboard ValidateDeviceLwM2MCredentials - * @param endpoint - - * @param keyValue - - * @return ValidateDeviceCredentialsResponseMsg and SecurityInfo - */ - public ReadResultSecurityStore createAndValidateCredentialsSecurityInfo(String endpoint, LwM2mTransportUtil.LwM2mTypeServer keyValue) { + + public EndpointSecurityInfo getEndpointSecurityInfo(String endpoint, LwM2mTransportUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); - final ReadResultSecurityStore[] resultSecurityStore = new ReadResultSecurityStore[1]; + final EndpointSecurityInfo[] resultSecurityStore = new EndpointSecurityInfo[1]; context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), new TransportServiceCallback<>() { @Override @@ -90,27 +85,27 @@ public class LwM2mCredentialsSecurityInfoValidator { /** * Create new SecurityInfo - * @param endPoint - + * @param endpoint - * @param jsonStr - * @param keyValue - * @return SecurityInfo */ - private ReadResultSecurityStore createSecurityInfo(String endPoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) { - ReadResultSecurityStore result = new ReadResultSecurityStore(); + private EndpointSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) { + EndpointSecurityInfo result = new EndpointSecurityInfo(); JsonObject objectMsg = LwM2mTransportUtil.validateJson(jsonStr); if (objectMsg != null && !objectMsg.isJsonNull()) { JsonObject object = (objectMsg.has(keyValue.type) && !objectMsg.get(keyValue.type).isJsonNull()) ? objectMsg.get(keyValue.type).getAsJsonObject() : null; /** * Only PSK */ - String endPointPsk = (objectMsg.has("client") + String endpointPsk = (objectMsg.has("client") && objectMsg.get("client").getAsJsonObject().has("endpoint") && objectMsg.get("client").getAsJsonObject().get("endpoint").isJsonPrimitive()) ? objectMsg.get("client").getAsJsonObject().get("endpoint").getAsString() : null; - endPoint = (endPointPsk == null || endPointPsk.isEmpty()) ? endPoint : endPointPsk; + endpoint = (endpointPsk == null || endpointPsk.isEmpty()) ? endpoint : endpointPsk; if (object != null && !object.isJsonNull()) { if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { result.setBootstrapJsonCredential(object); - result.setEndPoint(endPoint); + result.setEndPoint(endpoint); result.setSecurityMode(LwM2MSecurityMode.fromSecurityMode(object.get("bootstrapServer").getAsJsonObject().get("securityMode").getAsString().toLowerCase()).code); } else { LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase()); @@ -119,13 +114,13 @@ public class LwM2mCredentialsSecurityInfoValidator { createClientSecurityInfoNoSec(result); break; case PSK: - createClientSecurityInfoPSK(result, endPoint, object); + createClientSecurityInfoPSK(result, endpoint, object); break; case RPK: - createClientSecurityInfoRPK(result, endPoint, object); + createClientSecurityInfoRPK(result, endpoint, object); break; case X509: - createClientSecurityInfoX509(result, endPoint); + createClientSecurityInfoX509(result, endpoint); break; default: break; @@ -136,20 +131,20 @@ public class LwM2mCredentialsSecurityInfoValidator { return result; } - private void createClientSecurityInfoNoSec(ReadResultSecurityStore result) { + private void createClientSecurityInfoNoSec(EndpointSecurityInfo result) { result.setSecurityInfo(null); result.setSecurityMode(NO_SEC.code); } - private void createClientSecurityInfoPSK(ReadResultSecurityStore result, String endPoint, JsonObject object) { + private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, JsonObject object) { /** PSK Deserialization */ String identity = (object.has("identity") && object.get("identity").isJsonPrimitive()) ? object.get("identity").getAsString() : null; if (identity != null && !identity.isEmpty()) { try { byte[] key = (object.has("key") && object.get("key").isJsonPrimitive()) ? Hex.decodeHex(object.get("key").getAsString().toCharArray()) : null; if (key != null && key.length > 0) { - if (endPoint != null && !endPoint.isEmpty()) { - result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endPoint, identity, key)); + if (endpoint != null && !endpoint.isEmpty()) { + result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, identity, key)); result.setSecurityMode(PSK.code); } } @@ -161,7 +156,7 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoRPK(ReadResultSecurityStore result, String endpoint, JsonObject object) { + private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, JsonObject object) { try { if (object.has("key") && object.get("key").isJsonPrimitive()) { byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray()); @@ -176,7 +171,7 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoX509(ReadResultSecurityStore result, String endpoint) { + private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityMode(X509.code); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index 4ab206e856..e2e0184230 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -32,11 +32,11 @@ import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; -import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.server.registration.Registration; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.cache.firmware.FirmwareDataCache; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -66,8 +66,8 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Optional; @@ -77,7 +77,6 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -85,6 +84,7 @@ import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.CLIENT_NOT_AUTHORIZED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEVICE_ATTRIBUTES_REQUEST; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FR_OBJECT_ID; @@ -103,7 +103,6 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SERVICE_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @@ -115,9 +114,9 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.v @TbLwM2mTransportComponent public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler { - private ExecutorService executorRegistered; - private ExecutorService executorUpdateRegistered; - private ExecutorService executorUnRegistered; + private ExecutorService registrationExecutor; + private ExecutorService updateRegistrationExecutor; + private ExecutorService unregistrationExecutor; private LwM2mValueConverterImpl converter; private final TransportService transportService; @@ -126,18 +125,18 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler private final FirmwareDataCache firmwareDataCache; private final LwM2mTransportServerHelper helper; private final LwM2MJsonAdaptor adaptor; - private final LwM2mClientContext lwM2mClientContext; + private final LwM2mClientContext clientContext; private final LwM2mTransportRequest lwM2mTransportRequest; public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, - LwM2mClientContext lwM2mClientContext, + LwM2mClientContext clientContext, @Lazy LwM2mTransportRequest lwM2mTransportRequest, FirmwareDataCache firmwareDataCache, LwM2mTransportContext context, LwM2MJsonAdaptor adaptor) { this.transportService = transportService; this.config = config; this.helper = helper; - this.lwM2mClientContext = lwM2mClientContext; + this.clientContext = clientContext; this.lwM2mTransportRequest = lwM2mTransportRequest; this.firmwareDataCache = firmwareDataCache; this.context = context; @@ -146,13 +145,10 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler @PostConstruct public void init() { - this.context.getScheduler().scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS); - this.executorRegistered = Executors.newFixedThreadPool(this.config.getRegisteredPoolSize(), - new NamedThreadFactory(String.format("LwM2M %s channel registered", SERVICE_CHANNEL))); - this.executorUpdateRegistered = Executors.newFixedThreadPool(this.config.getUpdateRegisteredPoolSize(), - new NamedThreadFactory(String.format("LwM2M %s channel update registered", SERVICE_CHANNEL))); - this.executorUnRegistered = Executors.newFixedThreadPool(this.config.getUnRegisteredPoolSize(), - new NamedThreadFactory(String.format("LwM2M %s channel un registered", SERVICE_CHANNEL))); + this.context.getScheduler().scheduleAtFixedRate(this::reportActivity, new Random().nextInt((int) config.getSessionReportTimeout()), config.getSessionReportTimeout(), TimeUnit.MILLISECONDS); + this.registrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getRegisteredPoolSize(), "LwM2M registration"); + this.updateRegistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUpdateRegisteredPoolSize(), "LwM2M update registration"); + this.unregistrationExecutor = ThingsBoardExecutors.newWorkStealingPool(this.config.getUnRegisteredPoolSize(), "LwM2M unregistration"); this.converter = LwM2mValueConverterImpl.getInstance(); } @@ -171,14 +167,13 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param previousObservations - may be null */ public void onRegistered(Registration registration, Collection previousObservations) { - executorRegistered.submit(() -> { + registrationExecutor.submit(() -> { try { log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); - LwM2mClient lwM2MClient = this.lwM2mClientContext.updateInSessionsLwM2MClient(registration); + LwM2mClient lwM2MClient = this.clientContext.registerOrUpdate(registration); if (lwM2MClient != null) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); if (sessionInfo != null) { - this.initLwM2mClient(lwM2MClient, sessionInfo); transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN), null); transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); @@ -204,24 +199,18 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param registration - Registration LwM2M Client */ public void updatedReg(Registration registration) { - executorUpdateRegistered.submit(() -> { + updateRegistrationExecutor.submit(() -> { try { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); - if (sessionInfo != null) { - this.checkInactivity(sessionInfo); - LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2MClient(sessionInfo); - if (lwM2MClient.getDeviceId() == null && lwM2MClient.getProfileId() == null) { - initLwM2mClient(lwM2MClient, sessionInfo); - } else { - if (registration.getBindingMode().useQueueMode()) { - LwM2mQueuedRequest request; - while ((request = lwM2MClient.getQueuedRequests().poll()) != null) { - request.send(); - } + LwM2mClient client = clientContext.getOrRegister(registration); + if (client != null && client.getSession() != null) { + SessionInfoProto sessionInfo = client.getSession(); + this.reportActivityAndRegister(sessionInfo); + if (registration.getBindingMode().useQueueMode()) { + LwM2mQueuedRequest request; + while ((request = client.getQueuedRequests().poll()) != null) { + request.send(); } } - - log.info("Client: [{}] updatedReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); } else { log.error("Client: [{}] updatedReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); } @@ -237,7 +226,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect */ public void unReg(Registration registration, Collection observations) { - executorUnRegistered.submit(() -> { + unregistrationExecutor.submit(() -> { try { this.setCancelObservations(registration); this.sendLogsToThingsboard(LOG_LW2M_INFO + ": Client unRegistration", registration.getId()); @@ -248,22 +237,12 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler }); } - private void initLwM2mClient(LwM2mClient lwM2MClient, SessionInfoProto sessionInfo) { - lwM2MClient.setDeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - lwM2MClient.setProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - lwM2MClient.setDeviceName(sessionInfo.getDeviceName()); - lwM2MClient.setDeviceProfileName(sessionInfo.getDeviceType()); - } - private void closeClientSession(Registration registration) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration); if (sessionInfo != null) { transportService.deregisterSession(sessionInfo); this.doCloseSession(sessionInfo); - lwM2mClientContext.delRemoveSessionAndListener(registration.getId()); - if (lwM2mClientContext.getProfiles().size() > 0) { - this.syncSessionsAndProfiles(); - } + clientContext.removeClientByRegistrationId(registration.getId()); log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); } else { log.error("Client close session: [{}] unReg [{}] name [{}] sessionInfo ", registration.getId(), registration.getEndpoint(), null); @@ -331,12 +310,12 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); + LwM2mClient lwM2MClient = clientContext.getClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); if (msg.getSharedUpdatedCount() > 0) { msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, pathName); - Object valueNew = LwM2mTransportServerHelper.getValueFromKvProto(tsKvProto.getKv()); + Object valueNew = getValueFromKvProto(tsKvProto.getKv()); //TODO: react on change of the firmware name. if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { this.getInfoFirmwareUpdate(lwM2MClient); @@ -363,7 +342,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } else if (msg.getSharedDeletedCount() > 0) { msg.getSharedUpdatedList().forEach(tsKvProto -> { String pathName = tsKvProto.getKv().getKey(); - Object valueNew = LwM2mTransportServerHelper.getValueFromKvProto(tsKvProto.getKv()); + Object valueNew = getValueFromKvProto(tsKvProto.getKv()); if (FirmwareUtil.getAttributeKey(FirmwareType.FIRMWARE, FirmwareKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFrUpdate().getCurrentFwVersion())) { lwM2MClient.getFrUpdate().setCurrentFwVersion((String) valueNew); } @@ -378,45 +357,34 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ @Override public void onDeviceProfileUpdate(SessionInfoProto sessionInfo, DeviceProfile deviceProfile) { - Set registrationIds = lwM2mClientContext.getLwM2mClients().entrySet() - .stream() - .filter(e -> e.getValue().getProfileId().equals(deviceProfile.getUuidId())) - .map(Map.Entry::getKey).sorted().collect(Collectors.toCollection(LinkedHashSet::new)); + Set clients = clientContext.getLwM2mClients() + .stream().filter(e -> e.getProfileId().equals(deviceProfile.getUuidId())).collect(Collectors.toSet()); + clients.forEach(client -> client.onDeviceProfileUpdate(deviceProfile)); + Set registrationIds = clients.stream().map(LwM2mClient::getRegistration).map(Registration::getId).collect(Collectors.toSet()); if (registrationIds.size() > 0) { - this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile); + this.onDeviceProfileUpdate(registrationIds, deviceProfile); } } - /** - * @param sessionInfo - - * @param device - - * @param deviceProfileOpt - - */ @Override public void onDeviceUpdate(SessionInfoProto sessionInfo, Device device, Optional deviceProfileOpt) { - Optional registrationIdOpt = lwM2mClientContext.getLwM2mClients().entrySet().stream() - .filter(e -> device.getUuidId().equals(e.getValue().getDeviceId())) - .map(Map.Entry::getKey) - .findFirst(); - registrationIdOpt.ifPresent(registrationId -> this.onDeviceUpdateLwM2MClient(registrationId, device, deviceProfileOpt)); + //TODO: check, maybe device has multiple sessions/registrations? Is this possible according to the standard. + LwM2mClient client = clientContext.getClientByDeviceId(device.getUuidId()); + if (client != null) { + this.onDeviceUpdate(client, device, deviceProfileOpt); + } } - /** - * @param resourceUpdateMsgOpt - - */ @Override public void onResourceUpdate(Optional resourceUpdateMsgOpt) { String idVer = resourceUpdateMsgOpt.get().getResourceKey(); - lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.updateResourceModel(idVer, this.config.getModelProvider())); + clientContext.getLwM2mClients().forEach(e -> e.updateResourceModel(idVer, this.config.getModelProvider())); } - /** - * @param resourceDeleteMsgOpt - - */ @Override public void onResourceDelete(Optional resourceDeleteMsgOpt) { String pathIdVer = resourceDeleteMsgOpt.get().getResourceKey(); - lwM2mClientContext.getLwM2mClients().values().stream().forEach(e -> e.deleteResources(pathIdVer, this.config.getModelProvider())); + clientContext.getLwM2mClients().forEach(e -> e.deleteResources(pathIdVer, this.config.getModelProvider())); } @Override @@ -424,9 +392,9 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler Lwm2mClientRpcRequest lwm2mClientRpcRequest = null; try { log.info("[{}] toDeviceRpcRequest", toDeviceRequest); - Registration registration = lwM2mClientContext.getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).getRegistration(); + Registration registration = clientContext.getClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())).getRegistration(); lwm2mClientRpcRequest = this.getDeviceRpcRequest(toDeviceRequest, sessionInfo, registration); - if (lwm2mClientRpcRequest != null && lwm2mClientRpcRequest.getErrorMsg() != null) { + if (lwm2mClientRpcRequest.getErrorMsg() != null) { lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); } else { @@ -446,13 +414,6 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } } - /** - * @param toDeviceRequest - - * @param sessionInfo - - * @param registration - - * @return - * @throws IllegalArgumentException - */ private Lwm2mClientRpcRequest getDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest, SessionInfoProto sessionInfo, Registration registration) throws IllegalArgumentException { Lwm2mClientRpcRequest lwm2mClientRpcRequest = new Lwm2mClientRpcRequest(); @@ -590,35 +551,14 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } /** - * This method is used to sync with sessions - * Removes a profile if not used in sessions - */ - private void syncSessionsAndProfiles() { - Map profilesClone = lwM2mClientContext.getProfiles().entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - profilesClone.forEach((k, v) -> { - String registrationId = lwM2mClientContext.getLwM2mClients().entrySet() - .stream() - .filter(e -> e.getValue().getProfileId().equals(k)) - .findFirst() - .map(Map.Entry::getKey) // return the key of the matching entry if found - .orElse(""); - if (registrationId.isEmpty()) { - lwM2mClientContext.getProfiles().remove(k); - } - }); - } - - /** - * @param logMsg - text msg + * @param logMsg - text msg * @param registrationId - Id of Registration LwM2M Client */ @Override public void sendLogsToThingsboard(String logMsg, String registrationId) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId); + SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registrationId); if (logMsg != null && sessionInfo != null) { - if(logMsg.length() > 1024){ + if (logMsg.length() > 1024) { logMsg = logMsg.substring(0, 1024); } this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvLogyToThingsboard(logMsg), sessionInfo); @@ -638,8 +578,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param lwM2MClient - object with All parameters off client */ private void initLwM2mFromClientValue(Registration registration, LwM2mClient lwM2MClient) { - LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); - Set clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); + LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(registration); + Set clientObjects = clientContext.getSupportedIdVerInClient(registration); if (clientObjects != null && clientObjects.size() > 0) { if (LWM2M_STRATEGY_2 == LwM2mTransportUtil.getClientOnlyObserveAfterConnect(lwM2MClientProfile)) { // #2 @@ -693,7 +633,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param path - resource */ private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); + LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config .getModelProvider())) { if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && @@ -730,7 +670,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler private void updateAttrTelemetry(Registration registration, Set paths) { try { ResultsAddKeyValueProto results = getParametersFromProfile(registration, paths); - SessionInfoProto sessionInfo = this.getValidateSessionInfo(registration); + SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(registration); if (results != null && sessionInfo != null) { if (results.getResultAttributes().size() > 0) { this.helper.sendParametersOnThingsboardAttribute(results.getResultAttributes(), sessionInfo); @@ -752,7 +692,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ private void initReadAttrTelemetryObserveToClient(Registration registration, LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, Set clientObjects) { - LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); + LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(registration); Set result = null; ConcurrentHashMap params = null; if (READ.equals(typeOper)) { @@ -784,8 +724,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler lwM2MClient.getPendingReadRequests().addAll(pathSend); ConcurrentHashMap finalParams = params; pathSend.forEach(target -> { - lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), - finalParams != null ? finalParams.get(target) : null, this.config.getTimeout(), null); + lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), + finalParams != null ? finalParams.get(target) : null, this.config.getTimeout(), null); }); if (OBSERVE.equals(typeOper)) { lwM2MClient.initReadValue(this, null); @@ -801,23 +741,9 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler return pathAttributes; } - /** - * Update parameters device in LwM2MClient - * If new deviceProfile != old deviceProfile => update deviceProfile - * - * @param registrationId - - * @param device - - */ - private void onDeviceUpdateLwM2MClient(String registrationId, Device device, Optional deviceProfileOpt) { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClients().get(registrationId); - lwM2MClient.setDeviceName(device.getName()); - if (!lwM2MClient.getProfileId().equals(device.getDeviceProfileId().getId())) { - Set registrationIds = new HashSet<>(); - registrationIds.add(registrationId); - deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceUpdateChangeProfile(registrationIds, deviceProfile)); - } - - lwM2MClient.setProfileId(device.getDeviceProfileId().getId()); + private void onDeviceUpdate(LwM2mClient lwM2MClient, Device device, Optional deviceProfileOpt) { + deviceProfileOpt.ifPresent(deviceProfile -> this.onDeviceProfileUpdate(Collections.singleton(lwM2MClient.getRegistration().getId()), deviceProfile)); + lwM2MClient.onDeviceUpdate(device, deviceProfileOpt); } /** @@ -830,7 +756,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler private ResultsAddKeyValueProto getParametersFromProfile(Registration registration, Set path) { if (path != null && path.size() > 0) { ResultsAddKeyValueProto results = new ResultsAddKeyValueProto(); - LwM2mClientProfile lwM2MClientProfile = lwM2mClientContext.getProfile(registration); + LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(registration); List resultAttributes = new ArrayList<>(); lwM2MClientProfile.getPostAttributeProfile().forEach(pathIdVer -> { if (path.contains(pathIdVer.getAsString())) { @@ -861,8 +787,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } private TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) { - LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2mClientWithReg(null, registration.getId()); - JsonObject names = lwM2mClientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); + LwM2mClient lwM2MClient = this.clientContext.getClientByRegistrationId(registration.getId()); + JsonObject names = clientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); if (names != null && names.has(pathIdVer)) { String resourceName = names.get(pathIdVer).getAsString(); if (resourceName != null && !resourceName.isEmpty()) { @@ -960,9 +886,9 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param registrationIds - * @param deviceProfile - */ - private void onDeviceUpdateChangeProfile(Set registrationIds, DeviceProfile deviceProfile) { - LwM2mClientProfile lwM2MClientProfileOld = lwM2mClientContext.getProfiles().get(deviceProfile.getUuidId()).clone(); - if (lwM2mClientContext.addUpdateProfileParameters(deviceProfile)) { + private void onDeviceProfileUpdate(Set registrationIds, DeviceProfile deviceProfile) { + LwM2mClientProfile lwM2MClientProfileOld = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone(); + if (clientContext.toClientProfile(deviceProfile) != null) { // #1 JsonArray attributeOld = lwM2MClientProfileOld.getPostAttributeProfile(); Set attributeSetOld = convertJsonArrayToSet(attributeOld); @@ -972,7 +898,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile(); JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile(); - LwM2mClientProfile lwM2MClientProfileNew = lwM2mClientContext.getProfiles().get(deviceProfile.getUuidId()); + LwM2mClientProfile lwM2MClientProfileNew = clientContext.getProfiles().get(deviceProfile.getUuidId()); JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile(); Set attributeSetNew = convertJsonArrayToSet(attributeNew); JsonArray telemetryNew = lwM2MClientProfileNew.getPostTelemetryProfile(); @@ -1018,7 +944,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) { // update value in Resources registrationIds.forEach(registrationId -> { - Registration registration = lwM2mClientContext.getRegistration(registrationId); + Registration registration = clientContext.getRegistration(registrationId); this.readObserveFromProfile(registration, sendAttrToThingsboard.getPathPostParametersAdd(), READ); // send attr/telemetry to tingsboard for new path this.updateAttrTelemetry(registration, sendAttrToThingsboard.getPathPostParametersAdd()); @@ -1046,7 +972,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sendObserveToClientOld.getPathPostParametersAdd(), sendObserveToClientNew.getPathPostParametersAdd()); // send Request observe to Client registrationIds.forEach(registrationId -> { - Registration registration = lwM2mClientContext.getRegistration(registrationId); + Registration registration = clientContext.getRegistration(registrationId); if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) { this.readObserveFromProfile(registration, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE); } @@ -1151,8 +1077,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler // #6.2 if (analyzerParameters.getPathPostParametersAdd().size() > 0) { registrationIds.forEach(registrationId -> { - Registration registration = this.lwM2mClientContext.getRegistration(registrationId); - Set clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); + Registration registration = this.clientContext.getRegistration(registrationId); + Set clientObjects = clientContext.getSupportedIdVerInClient(registration); Set pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { @@ -1165,8 +1091,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler // #6.2 if (analyzerParameters.getPathPostParametersDel().size() > 0) { registrationIds.forEach(registrationId -> { - Registration registration = this.lwM2mClientContext.getRegistration(registrationId); - Set clientObjects = lwM2mClientContext.getSupportedIdVerInClient(registration); + Registration registration = this.clientContext.getRegistration(registrationId); + Set clientObjects = clientContext.getSupportedIdVerInClient(registration); Set pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { @@ -1184,7 +1110,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } private void cancelObserveFromProfile(Registration registration, Set paramAnallyzer) { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); + LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); paramAnallyzer.forEach(pathIdVer -> { if (this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) != null) { lwM2mTransportRequest.sendAllRequest(registration, pathIdVer, OBSERVE_CANCEL, null, @@ -1224,8 +1150,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @return - */ private String getPresentPathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { - LwM2mClientProfile profile = lwM2mClientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); - LwM2mClient lwM2mClient = lwM2mClientContext.getLwM2MClient(sessionInfo); + LwM2mClientProfile profile = clientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + LwM2mClient lwM2mClient = clientContext.getClient(sessionInfo); return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() .filter(e -> e.getValue().getAsString().equals(name) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().map(Map.Entry::getKey) .orElse(null); @@ -1244,7 +1170,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler try { List tsKvProtos = attributesResponse.getSharedAttributeListList(); - this.updateAttriuteFromThingsboard(tsKvProtos, sessionInfo); + this.updateAttributeFromThingsboard(tsKvProtos, sessionInfo); } catch (Exception e) { log.error(String.valueOf(e)); } @@ -1260,8 +1186,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param tsKvProtos * @param sessionInfo */ - public void updateAttriuteFromThingsboard(List tsKvProtos, TransportProtos.SessionInfoProto sessionInfo) { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2MClient(sessionInfo); + public void updateAttributeFromThingsboard(List tsKvProtos, TransportProtos.SessionInfoProto sessionInfo) { + LwM2mClient lwM2MClient = clientContext.getClient(sessionInfo); tsKvProtos.forEach(tsKvProto -> { String pathIdVer = this.getPresentPathIntoProfile(sessionInfo, tsKvProto.getKv().getKey()); if (pathIdVer != null) { @@ -1276,7 +1202,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler // #2.1 lwM2MClient.getDelayedRequests().forEach((pathIdVer, tsKvProto) -> { this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), - this.helper.getValueFromKvProto(tsKvProto.getKv()), pathIdVer); + getValueFromKvProto(tsKvProto.getKv()), pathIdVer); }); } @@ -1284,30 +1210,14 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param lwM2MClient - * @return SessionInfoProto - */ - private SessionInfoProto getNewSessionInfoProto(LwM2mClient lwM2MClient) { + private SessionInfoProto getSessionInfoOrCloseSession(LwM2mClient lwM2MClient) { if (lwM2MClient != null) { - TransportProtos.ValidateDeviceCredentialsResponseMsg msg = lwM2MClient.getCredentialsResponse(); - if (msg == null) { - log.error("[{}] [{}]", lwM2MClient.getEndpoint(), CLIENT_NOT_AUTHORIZED); + SessionInfoProto sessionInfoProto = lwM2MClient.getSession(); + if (sessionInfoProto == null) { + log.info("[{}] [{}]", lwM2MClient.getEndpoint(), CLIENT_NOT_AUTHORIZED); this.closeClientSession(lwM2MClient.getRegistration()); - return null; - } else { - return SessionInfoProto.newBuilder() - .setNodeId(this.context.getNodeId()) - .setSessionIdMSB(lwM2MClient.getSessionId().getMostSignificantBits()) - .setSessionIdLSB(lwM2MClient.getSessionId().getLeastSignificantBits()) - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) - .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB()) - .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB()) - .setDeviceName(msg.getDeviceInfo().getDeviceName()) - .setDeviceType(msg.getDeviceInfo().getDeviceType()) - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) - .build(); } + return sessionInfoProto; } return null; } @@ -1316,18 +1226,16 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param registration - Registration LwM2M Client * @return - sessionInfo after access connect client */ - private SessionInfoProto getValidateSessionInfo(Registration registration) { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); - return getNewSessionInfoProto(lwM2MClient); + private SessionInfoProto getSessionInfoOrCloseSession(Registration registration) { + return getSessionInfoOrCloseSession(clientContext.getOrRegister(registration)); } /** * @param registrationId - * @return - */ - private SessionInfoProto getValidateSessionInfo(String registrationId) { - LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(null, registrationId); - return lwM2MClient != null ? this.getNewSessionInfoProto(lwM2MClient) : null; + private SessionInfoProto getSessionInfoOrCloseSession(String registrationId) { + return getSessionInfoOrCloseSession(clientContext.getClientByRegistrationId(registrationId)); } /** @@ -1335,14 +1243,14 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * * @param sessionInfo - */ - private void checkInactivity(SessionInfoProto sessionInfo) { - if (transportService.reportActivity(sessionInfo) == null) { + private void reportActivityAndRegister(SessionInfoProto sessionInfo) { + if (sessionInfo != null && transportService.reportActivity(sessionInfo) == null) { transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); } } - private void checkInactivityAndReportActivity() { - lwM2mClientContext.getLwM2mClients().forEach((key, value) -> this.checkInactivity(this.getValidateSessionInfo(key))); + private void reportActivity() { + clientContext.getLwM2mClients().forEach(client -> reportActivityAndRegister(client.getSession())); } /** @@ -1356,7 +1264,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param lwM2MClient - LwM2M Client */ public void putDelayedUpdateResourcesThingsboard(LwM2mClient lwM2MClient) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); + SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); if (sessionInfo != null) { //#1.1 ConcurrentMap keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); @@ -1374,7 +1282,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) { - SessionInfoProto sessionInfo = this.getValidateSessionInfo(lwM2MClient.getRegistration()); + SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); if (sessionInfo != null) { TransportProtos.GetFirmwareRequestMsg getFirmwareRequestMsg = TransportProtos.GetFirmwareRequestMsg.newBuilder() .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) @@ -1415,7 +1323,6 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } /** - * * @param lwM2MClient - */ public void updateFirmwareClient(LwM2mClient lwM2MClient) { @@ -1441,7 +1348,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ private ConcurrentMap getNamesFromProfileForSharedAttributes(LwM2mClient lwM2MClient) { - LwM2mClientProfile profile = lwM2mClientContext.getProfile(lwM2MClient.getProfileId()); + LwM2mClientProfile profile = clientContext.getProfile(lwM2MClient.getProfileId()); return new Gson().fromJson(profile.getPostKeyNameProfile().toString(), new TypeToken>() { }.getType()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index 4e0b0740b1..92a2a59e9a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -115,7 +115,7 @@ public class LwM2mTransportRequest { String target = convertPathFromIdVerToObjectId(targetIdVer); DownlinkRequest request = null; ContentFormat contentFormat = contentFormatName != null ? ContentFormat.fromName(contentFormatName.toUpperCase()) : ContentFormat.DEFAULT; - LwM2mClient lwM2MClient = this.lwM2mClientContext.getLwM2mClientWithReg(registration, null); + LwM2mClient lwM2MClient = this.lwM2mClientContext.getOrRegister(registration); LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null; if (!OBSERVE_READ_ALL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && lwM2MClient != null) { if (lwM2MClient.isValidObjectVersion(targetIdVer)) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java index 6a8e74f229..8b6e46172a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportUtil.java @@ -183,7 +183,6 @@ public class LwM2mTransportUtil { } public static final String EVENT_AWAKE = "AWAKE"; - public static final String SERVICE_CHANNEL = "SERVICE"; public static final String RESPONSE_CHANNEL = "RESP"; public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException { @@ -249,7 +248,7 @@ public class LwM2mTransportUtil { * "/3_1.0/0": {"gt": 17}, * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}} */ - public static LwM2mClientProfile getLwM2MClientProfileFromThingsboard(DeviceProfile deviceProfile) { + public static LwM2mClientProfile toLwM2MClientProfile(DeviceProfile deviceProfile) { if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { Object profile = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); try { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 8577e177c4..a934779e03 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -16,6 +16,8 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.Data; +import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; @@ -24,7 +26,11 @@ import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.SecurityInfo; +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.TsKvProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; @@ -33,6 +39,7 @@ import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.Set; import java.util.UUID; @@ -47,31 +54,52 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId; @Slf4j -@Data public class LwM2mClient implements Cloneable { + @Getter private String deviceName; + @Getter private String deviceProfileName; + @Getter private String endpoint; + @Getter private String identity; + @Getter private SecurityInfo securityInfo; + @Getter private UUID deviceId; + @Getter private UUID sessionId; + @Getter + private SessionInfoProto session; + @Getter private UUID profileId; + @Getter + @Setter private volatile LwM2mFirmwareUpdate frUpdate; + @Getter + @Setter private Registration registration; + private ValidateDeviceCredentialsResponseMsg credentialsResponse; + @Getter private final Map resources; - private final Map delayedRequests; + @Getter + private final Map delayedRequests; + @Getter private final List pendingReadRequests; + @Getter private final Queue queuedRequests; + @Getter private boolean init; + @Getter + @Setter private volatile boolean updateFw; public Object clone() throws CloneNotSupportedException { return super.clone(); } - public LwM2mClient(String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileId, UUID sessionId) { + public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileId, UUID sessionId) { this.endpoint = endpoint; this.identity = identity; this.securityInfo = securityInfo; @@ -85,6 +113,56 @@ public class LwM2mClient implements Cloneable { this.updateFw = false; this.queuedRequests = new ConcurrentLinkedQueue<>(); this.frUpdate = new LwM2mFirmwareUpdate(); + if (this.credentialsResponse != null && this.credentialsResponse.hasDeviceInfo()) { + this.session = createSession(nodeId, sessionId, credentialsResponse); + this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB()); + this.profileId = new UUID(session.getDeviceProfileIdMSB(), session.getDeviceProfileIdLSB()); + this.deviceName = session.getDeviceName(); + this.deviceProfileName = session.getDeviceType(); + } + } + + public void onDeviceUpdate(Device device, Optional deviceProfileOpt) { + SessionInfoProto.Builder builder = SessionInfoProto.newBuilder().mergeFrom(session); + this.deviceId = device.getUuidId(); + this.deviceName = device.getName(); + builder.setDeviceIdMSB(deviceId.getMostSignificantBits()); + builder.setDeviceIdLSB(deviceId.getLeastSignificantBits()); + builder.setDeviceName(deviceName); + deviceProfileOpt.ifPresent(deviceProfile -> updateSession(deviceProfile, builder)); + this.session = builder.build(); + } + + public void onDeviceProfileUpdate(DeviceProfile deviceProfile) { + SessionInfoProto.Builder builder = SessionInfoProto.newBuilder().mergeFrom(session); + updateSession(deviceProfile, builder); + this.session = builder.build(); + } + + private void updateSession(DeviceProfile deviceProfile, SessionInfoProto.Builder builder) { + this.deviceProfileName = deviceProfile.getName(); + this.profileId = deviceProfile.getUuidId(); + builder.setDeviceProfileIdMSB(profileId.getMostSignificantBits()); + builder.setDeviceProfileIdLSB(profileId.getLeastSignificantBits()); + builder.setDeviceType(this.deviceProfileName); + } + + private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponseMsg msg) { + return SessionInfoProto.newBuilder() + .setNodeId(nodeId) + .setSessionIdMSB(sessionId.getMostSignificantBits()) + .setSessionIdLSB(sessionId.getLeastSignificantBits()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) + .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) + .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) + .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) + .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB()) + .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB()) + .setDeviceName(msg.getDeviceInfo().getDeviceName()) + .setDeviceType(msg.getDeviceInfo().getDeviceType()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .build(); } public boolean saveResourceValue(String pathRez, LwM2mResource rez, LwM2mModelProvider modelProvider) { @@ -123,7 +201,7 @@ public class LwM2mClient implements Cloneable { return resources; } - public boolean isValidObjectVersion (String path) { + public boolean isValidObjectVersion(String path) { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(path)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verRez = getVerFromPathIdVerOrId(path); @@ -131,8 +209,7 @@ public class LwM2mClient implements Cloneable { } /** - * - * @param pathIdVer == "3_1.0" + * @param pathIdVer == "3_1.0" * @param modelProvider - */ public void deleteResources(String pathIdVer, LwM2mModelProvider modelProvider) { @@ -142,16 +219,14 @@ public class LwM2mClient implements Cloneable { ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); if (resourceModel != null) { this.resources.get(pathRez).setResourceModel(resourceModel); - } - else { + } else { this.resources.remove(pathRez); } }); } /** - * - * @param idVer - + * @param idVer - * @param modelProvider - */ public void updateResourceModel(String idVer, LwM2mModelProvider modelProvider) { @@ -182,8 +257,5 @@ public class LwM2mClient implements Cloneable { } } - public LwM2mClient copy() { - return new LwM2mClient(this.endpoint, this.identity, this.securityInfo, this.credentialsResponse, this.profileId, this.sessionId); - } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java index 358f760fef..8e7f6a70ec 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java @@ -19,29 +19,32 @@ import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos; +import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.UUID; public interface LwM2mClientContext { - void delRemoveSessionAndListener(String registrationId); + void removeClientByRegistrationId(String registrationId); - LwM2mClient getLwM2MClient(String endPoint, String identity); + LwM2mClient getClientByEndpoint(String endpoint); - LwM2mClient getLwM2MClient(TransportProtos.SessionInfoProto sessionInfo); + LwM2mClient getClientByRegistrationId(String registrationId); - LwM2mClient getLwM2mClient(UUID sessionId); + LwM2mClient getClient(TransportProtos.SessionInfoProto sessionInfo); - LwM2mClient getLwM2mClientWithReg(Registration registration, String registrationId); + LwM2mClient getClient(UUID sessionId); - LwM2mClient updateInSessionsLwM2MClient(Registration registration); + LwM2mClient getOrRegister(Registration registration); - LwM2mClient addLwM2mClientToSession(String identity); + LwM2mClient registerOrUpdate(Registration registration); + + LwM2mClient fetchClientByEndpoint(String endpoint); Registration getRegistration(String registrationId); - Map getLwM2mClients(); + Collection getLwM2mClients(); Map getProfiles(); @@ -51,7 +54,9 @@ public interface LwM2mClientContext { Map setProfiles(Map profiles); - boolean addUpdateProfileParameters(DeviceProfile deviceProfile); + LwM2mClientProfile toClientProfile(DeviceProfile deviceProfile); Set getSupportedIdVerInClient(Registration registration); + + LwM2mClient getClientByDeviceId(UUID deviceId); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java index d209d8d829..4afe54c056 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContextImpl.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.client; +import lombok.RequiredArgsConstructor; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.EditableSecurityStore; @@ -24,123 +25,121 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; -import org.thingsboard.server.transport.lwm2m.secure.ReadResultSecurityStore; +import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.util.Arrays; +import java.util.Collection; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @Service @TbLwM2mTransportComponent +@RequiredArgsConstructor public class LwM2mClientContextImpl implements LwM2mClientContext { - private static final boolean INFOS_ARE_COMPROMISED = false; - - private final Map lwM2mClients = new ConcurrentHashMap<>(); - private Map profiles = new ConcurrentHashMap<>(); + private final LwM2mTransportContext context; + private final Map lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); + private final Map lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); + private Map profiles = new ConcurrentHashMap<>(); private final LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator; private final EditableSecurityStore securityStore; - public LwM2mClientContextImpl(LwM2mCredentialsSecurityInfoValidator lwM2MCredentialsSecurityInfoValidator, EditableSecurityStore securityStore) { - this.lwM2MCredentialsSecurityInfoValidator = lwM2MCredentialsSecurityInfoValidator; - this.securityStore = securityStore; - } - - public void delRemoveSessionAndListener(String registrationId) { - LwM2mClient lwM2MClient = this.lwM2mClients.get(registrationId); - if (lwM2MClient != null) { - this.securityStore.remove(lwM2MClient.getEndpoint(), INFOS_ARE_COMPROMISED); - this.lwM2mClients.remove(registrationId); - } + @Override + public LwM2mClient getClientByEndpoint(String endpoint) { + return lwM2mClientsByEndpoint.get(endpoint); } @Override - public LwM2mClient getLwM2MClient(String endPoint, String identity) { - Map.Entry modelClients = endPoint != null ? - this.lwM2mClients.entrySet().stream().filter(model -> endPoint.equals(model.getValue().getEndpoint())).findAny().orElse(null) : - this.lwM2mClients.entrySet().stream().filter(model -> identity.equals(model.getValue().getIdentity())).findAny().orElse(null); - return modelClients != null ? modelClients.getValue() : null; + public LwM2mClient getClientByRegistrationId(String registrationId) { + return lwM2mClientsByRegistrationId.get(registrationId); } @Override - public LwM2mClient getLwM2MClient(TransportProtos.SessionInfoProto sessionInfo) { - return getLwM2mClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); + public LwM2mClient getOrRegister(Registration registration) { + if (registration == null) { + return null; + } + LwM2mClient client = lwM2mClientsByRegistrationId.get(registration.getId()); + if (client == null) { + client = lwM2mClientsByEndpoint.get(registration.getEndpoint()); + if (client == null) { + client = registerOrUpdate(registration); + } + } + return client; } @Override - public LwM2mClient getLwM2mClient(UUID sessionId) { - return lwM2mClients.values().stream().filter(c -> c.getSessionId().equals(sessionId)).findAny().get(); + public LwM2mClient getClient(TransportProtos.SessionInfoProto sessionInfo) { + return getClient(new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); } @Override - public LwM2mClient getLwM2mClientWithReg(Registration registration, String registrationId) { - LwM2mClient client = registrationId != null && this.lwM2mClients.containsKey(registrationId) ? - this.lwM2mClients.get(registrationId) : - registration !=null && this.lwM2mClients.containsKey(registration.getId()) ? - this.lwM2mClients.get(registration.getId()) : registration !=null && this.lwM2mClients.containsKey(registration) ? - this.lwM2mClients.get(registration.getEndpoint()) : null; - return client != null ? client : registration!= null ? updateInSessionsLwM2MClient(registration) : null; + public LwM2mClient getClient(UUID sessionId) { + //TODO: refactor this to search by sessionId efficiently. + return lwM2mClientsByEndpoint.values().stream().filter(c -> c.getSessionId().equals(sessionId)).findAny().get(); } @Override - public LwM2mClient updateInSessionsLwM2MClient(Registration registration) { - if (this.lwM2mClients.get(registration.getEndpoint()) == null) { - this.addLwM2mClientToSession(registration.getEndpoint()); + public LwM2mClient registerOrUpdate(Registration registration) { + LwM2mClient lwM2MClient = lwM2mClientsByEndpoint.get(registration.getEndpoint()); + if (lwM2MClient == null) { + lwM2MClient = this.fetchClientByEndpoint(registration.getEndpoint()); } - LwM2mClient lwM2MClient = lwM2mClients.get(registration.getEndpoint()); lwM2MClient.setRegistration(registration); - this.lwM2mClients.remove(registration.getEndpoint()); - this.lwM2mClients.put(registration.getId(), lwM2MClient); +// TODO: this remove is probably redundant. We should remove it. +// this.lwM2mClientsByEndpoint.remove(registration.getEndpoint()); + this.lwM2mClientsByRegistrationId.put(registration.getId(), lwM2MClient); return lwM2MClient; } public Registration getRegistration(String registrationId) { - return this.lwM2mClients.get(registrationId).getRegistration(); + return this.lwM2mClientsByRegistrationId.get(registrationId).getRegistration(); } - /** - * Add new LwM2MClient to session - * - * @param identity- - * @return SecurityInfo. If error - SecurityInfoError - * and log: - * - FORBIDDEN - if there is no authorization - * - profileUuid - if the device does not have a profile - * - device - if the thingsboard does not have a device with a name equal to the identity - */ @Override - public LwM2mClient addLwM2mClientToSession(String identity) { - ReadResultSecurityStore store = lwM2MCredentialsSecurityInfoValidator.createAndValidateCredentialsSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); - if (store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { - UUID profileUuid = (store.getDeviceProfile() != null && addUpdateProfileParameters(store.getDeviceProfile())) ? store.getDeviceProfile().getUuidId() : null; - LwM2mClient client; - if (store.getSecurityInfo() != null && profileUuid != null) { - String endpoint = store.getSecurityInfo().getEndpoint(); - client = new LwM2mClient(endpoint, store.getSecurityInfo().getIdentity(), store.getSecurityInfo(), store.getMsg(), profileUuid, UUID.randomUUID()); - lwM2mClients.put(endpoint, client); - } else if (store.getSecurityMode() == NO_SEC.code && profileUuid != null) { - client = new LwM2mClient(identity, null, null, store.getMsg(), profileUuid, UUID.randomUUID()); - lwM2mClients.put(identity, client); + public LwM2mClient fetchClientByEndpoint(String endpoint) { + EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); + if (securityInfo.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (securityInfo.getDeviceProfile() != null) { + toClientProfile(securityInfo.getDeviceProfile()); + UUID profileUuid = securityInfo.getDeviceProfile().getUuidId(); + LwM2mClient client; + if (securityInfo.getSecurityInfo() != null) { + client = new LwM2mClient(context.getNodeId(), securityInfo.getSecurityInfo().getEndpoint(), + securityInfo.getSecurityInfo().getIdentity(), securityInfo.getSecurityInfo(), + securityInfo.getMsg(), profileUuid, UUID.randomUUID()); + } else if (securityInfo.getSecurityMode() == NO_SEC.code) { + client = new LwM2mClient(context.getNodeId(), endpoint, + null, null, + securityInfo.getMsg(), profileUuid, UUID.randomUUID()); + } else { + throw new RuntimeException(String.format("Registration failed: device %s not found.", endpoint)); + } + lwM2mClientsByEndpoint.put(client.getEndpoint(), client); + return client; } else { - throw new RuntimeException(String.format("Registration failed: FORBIDDEN/profileUuid/device %s , endpointId: %s [PSK]", profileUuid, identity)); + throw new RuntimeException(String.format("Registration failed: device %s not found.", endpoint)); } - return client; } else { - throw new RuntimeException(String.format("Registration failed: FORBIDDEN, endpointId: %s", identity)); + throw new RuntimeException(String.format("Registration failed: FORBIDDEN, endpointId: %s", endpoint)); } } @Override - public Map getLwM2mClients() { - return lwM2mClients; + public Collection getLwM2mClients() { + return lwM2mClientsByEndpoint.values(); } @Override @@ -155,7 +154,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public LwM2mClientProfile getProfile(Registration registration) { - return this.getProfiles().get(getLwM2mClientWithReg(registration, null).getProfileId()); + return this.getProfiles().get(getOrRegister(registration).getProfileId()); } @Override @@ -164,17 +163,18 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } @Override - public boolean addUpdateProfileParameters(DeviceProfile deviceProfile) { - LwM2mClientProfile lwM2MClientProfile = LwM2mTransportUtil.getLwM2MClientProfileFromThingsboard(deviceProfile); - if (lwM2MClientProfile != null) { + public LwM2mClientProfile toClientProfile(DeviceProfile deviceProfile) { + LwM2mClientProfile lwM2MClientProfile = profiles.get(deviceProfile.getUuidId()); + if (lwM2MClientProfile == null) { + lwM2MClientProfile = LwM2mTransportUtil.toLwM2MClientProfile(deviceProfile); profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); - return true; } - return false; + return lwM2MClientProfile; } /** * if isVer - ok or default ver=DEFAULT_LWM2M_VERSION + * * @param registration - * @return - all objectIdVer in client */ @@ -184,9 +184,31 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { Arrays.stream(registration.getObjectLinks()).forEach(url -> { LwM2mPath pathIds = new LwM2mPath(url.getUrl()); if (!pathIds.isRoot()) { - clientObjects.add(convertPathFromObjectIdToIdVer(url.getUrl(), registration)); + clientObjects.add(convertPathFromObjectIdToIdVer(url.getUrl(), registration)); } }); return (clientObjects.size() > 0) ? clientObjects : null; } + + @Override + public LwM2mClient getClientByDeviceId(UUID deviceId) { + return lwM2mClientsByRegistrationId.values().stream().filter(e -> deviceId.equals(e.getDeviceId())).findFirst().orElse(null); + } + + @Override + public void removeClientByRegistrationId(String registrationId) { + LwM2mClient lwM2MClient = this.lwM2mClientsByRegistrationId.get(registrationId); + if (lwM2MClient != null) { + this.securityStore.remove(lwM2MClient.getEndpoint(), false); + this.lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint()); + this.lwM2mClientsByRegistrationId.remove(registrationId); + UUID profileId = lwM2MClient.getProfileId(); + if (profileId != null) { + Optional otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst(); + if (otherClients.isEmpty()) { + profiles.remove(profileId); + } + } + } + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java new file mode 100644 index 0000000000..e6ed99c5e6 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java @@ -0,0 +1,94 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server.store; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.server.security.EditableSecurityStore; +import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; +import org.eclipse.leshan.server.security.SecurityInfo; +import org.eclipse.leshan.server.security.SecurityStoreListener; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; + +import java.util.Collection; + +@Slf4j +public class TbLwM2mSecurityStore implements EditableSecurityStore { + + private final LwM2mClientContext clientContext; + private final EditableSecurityStore securityStore; + + public TbLwM2mSecurityStore(LwM2mClientContext clientContext, EditableSecurityStore securityStore) { + this.clientContext = clientContext; + this.securityStore = securityStore; + } + + @Override + public Collection getAll() { + return securityStore.getAll(); + } + + @Override + public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { + return securityStore.add(info); + } + + @Override + public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { + return securityStore.remove(endpoint, infosAreCompromised); + } + + @Override + public void setListener(SecurityStoreListener listener) { + securityStore.setListener(listener); + } + + @Override + public SecurityInfo getByEndpoint(String endpoint) { + SecurityInfo securityInfo = securityStore.getByEndpoint(endpoint); + if (securityInfo == null) { + LwM2mClient lwM2mClient = clientContext.getClientByEndpoint(endpoint); + if (lwM2mClient != null && lwM2mClient.getRegistration() != null && !lwM2mClient.getRegistration().getIdentity().isSecure()) { + return null; + } + securityInfo = clientContext.fetchClientByEndpoint(endpoint).getSecurityInfo(); + try { + if (securityInfo != null) { + add(securityInfo); + } + } catch (NonUniqueSecurityInfoException e) { + log.warn("Failed to add security info: {}", securityInfo, e); + } + } + return securityInfo; + } + + @Override + public SecurityInfo getByIdentity(String pskIdentity) { + SecurityInfo securityInfo = securityStore.getByIdentity(pskIdentity); + if (securityInfo == null) { + securityInfo = clientContext.fetchClientByEndpoint(pskIdentity).getSecurityInfo(); + try { + if (securityInfo != null) { + add(securityInfo); + } + } catch (NonUniqueSecurityInfoException e) { + log.warn("Failed to add security info: {}", securityInfo, e); + } + } + return securityInfo; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreConfiguration.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreConfiguration.java deleted file mode 100644 index 679e95bdbb..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreConfiguration.java +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright © 2016-2021 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.lwm2m.server.store; - -import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; -import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; -import org.eclipse.leshan.server.security.EditableSecurityStore; -import org.eclipse.leshan.server.security.InMemorySecurityStore; -import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; -import org.eclipse.leshan.server.security.SecurityInfo; -import org.eclipse.leshan.server.security.SecurityStoreListener; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Lazy; -import org.springframework.stereotype.Service; -import org.thingsboard.server.cache.TBRedisCacheConfiguration; -import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; - -import java.util.Collection; -import java.util.Optional; - -@Service -@TbLwM2mTransportComponent -public class TbLwM2mStoreConfiguration { - - @Autowired(required = false) - private Optional redisConfiguration; - - @Autowired - @Lazy - private LwM2mClientContext clientContext; - - @Value("${transport.lwm2m.redis.enabled:false}") - private boolean useRedis; - - @Bean - private CaliforniumRegistrationStore registrationStore() { - return redisConfiguration.isPresent() && useRedis ? - new TbLwM2mRedisRegistrationStore(redisConfiguration.get().redisConnectionFactory()) : new InMemoryRegistrationStore(); - } - - @Bean - private EditableSecurityStore securityStore() { - return new TbLwM2mSecurityStoreWrapper(redisConfiguration.isPresent() && useRedis ? - new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new InMemorySecurityStore()); - } - - public class TbLwM2mSecurityStoreWrapper implements EditableSecurityStore { - - private final EditableSecurityStore securityStore; - - public TbLwM2mSecurityStoreWrapper(EditableSecurityStore securityStore) { - this.securityStore = securityStore; - } - - @Override - public Collection getAll() { - return securityStore.getAll(); - } - - @Override - public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { - return securityStore.add(info); - } - - @Override - public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { - return securityStore.remove(endpoint, infosAreCompromised); - } - - @Override - public void setListener(SecurityStoreListener listener) { - securityStore.setListener(listener); - } - - @Override - public SecurityInfo getByEndpoint(String endPoint) { - SecurityInfo securityInfo = securityStore.getByEndpoint(endPoint); - if (securityInfo == null) { - LwM2mClient lwM2mClient = clientContext.getLwM2MClient(endPoint, null); - if (lwM2mClient != null && lwM2mClient.getRegistration() != null && !lwM2mClient.getRegistration().getIdentity().isSecure()){ - return null; - } - securityInfo = clientContext.addLwM2mClientToSession(endPoint).getSecurityInfo(); - try { - if (securityInfo != null) { - add(securityInfo); - } - } catch (NonUniqueSecurityInfoException e) { - e.printStackTrace(); - } - } - return securityInfo; - } - - @Override - public SecurityInfo getByIdentity(String pskIdentity) { - SecurityInfo securityInfo = securityStore.getByIdentity(pskIdentity); - if (securityInfo == null) { - securityInfo = clientContext.addLwM2mClientToSession(pskIdentity).getSecurityInfo(); - try { - if (securityInfo != null) { - add(securityInfo); - } - } catch (NonUniqueSecurityInfoException e) { - e.printStackTrace(); - } - } - return securityInfo; - } - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java new file mode 100644 index 0000000000..1fcbd15e33 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mStoreFactory.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2021 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.lwm2m.server.store; + +import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; +import org.eclipse.leshan.server.californium.registration.InMemoryRegistrationStore; +import org.eclipse.leshan.server.security.EditableSecurityStore; +import org.eclipse.leshan.server.security.InMemorySecurityStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Component; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; + +import java.util.Optional; + +@Component +@TbLwM2mTransportComponent +public class TbLwM2mStoreFactory { + + @Autowired(required = false) + private Optional redisConfiguration; + + @Autowired + @Lazy + private LwM2mClientContext clientContext; + + @Value("${transport.lwm2m.redis.enabled:false}") + private boolean useRedis; + + @Bean + private CaliforniumRegistrationStore registrationStore() { + return redisConfiguration.isPresent() && useRedis ? + new TbLwM2mRedisRegistrationStore(redisConfiguration.get().redisConnectionFactory()) : new InMemoryRegistrationStore(); + } + + @Bean + private EditableSecurityStore securityStore() { + return new TbLwM2mSecurityStore(clientContext, redisConfiguration.isPresent() && useRedis ? + new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new InMemorySecurityStore()); + } + +} From 3fb993b48e92c04f0465f322f40bbb25d727b008 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 5 May 2021 11:29:33 +0300 Subject: [PATCH 125/127] fixed firmware dashboard (added deviceProfileId to the firmware autocomplete) --- application/src/main/data/json/demo/dashboards/firmware.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/data/json/demo/dashboards/firmware.json b/application/src/main/data/json/demo/dashboards/firmware.json index f7dfd7e380..6e8d18cd98 100644 --- a/application/src/main/data/json/demo/dashboards/firmware.json +++ b/application/src/main/data/json/demo/dashboards/firmware.json @@ -247,7 +247,7 @@ "name": "Edit firmware", "icon": "edit", "type": "customPretty", - "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", + "customHtml": "
\n \n

Edit firmware {{entityName}}

\n \n \n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n
", "customCss": "", "customFunction": "let $injector = widgetContext.$scope.$injector;\nlet customDialog = $injector.get(widgetContext.servicesMap.get('customDialog'));\nlet entityService = $injector.get(widgetContext.servicesMap.get('entityService'));\nlet deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));\n\nopenEditEntityDialog();\n\nfunction openEditEntityDialog() {\n customDialog.customDialog(htmlTemplate, EditEntityDialogController).subscribe();\n}\n\nfunction EditEntityDialogController(instance) {\n let vm = instance;\n\n vm.entityName = entityName;\n vm.entity = {};\n\n vm.editEntityFormGroup = vm.fb.group({\n firmwareId: [null]\n });\n\n getEntityInfo();\n\n vm.cancel = function() {\n vm.dialogRef.close(null);\n };\n\n vm.save = function() {\n vm.editEntityFormGroup.markAsPristine();\n saveEntity().subscribe(\n function () {\n // widgetContext.updateAliases();\n vm.dialogRef.close(null);\n }\n );\n };\n\n\n function getEntityInfo() {\n entityService.getEntity(entityId.entityType, entityId.id).subscribe(\n function (data) {\n vm.entity = data;\n vm.editEntityFormGroup.patchValue({\n firmwareId: vm.entity.firmwareId\n }, {emitEvent: false});\n }\n );\n }\n\n function saveEntity() {\n const formValues = vm.editEntityFormGroup.value;\n vm.entity.firmwareId = formValues.firmwareId;\n return deviceService.saveDevice(vm.entity);\n }\n}", "customResources": [], From 50209d91fe161c32e03d7fff8f20946b3feda52e Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 5 May 2021 14:53:53 +0300 Subject: [PATCH 126/127] Lwm2m: add Write_Update many --- .../DefaultLwM2MTransportMsgHandler.java | 46 +++++++++++-------- .../server/LwM2mTransportHandlerUtil.java | 27 +++++++++++ .../lwm2m/server/LwM2mTransportRequest.java | 19 +++++--- .../lwm2m/server/client/LwM2mClient.java | 30 +++++++++--- .../lwm2m/utils/LwM2mValueConverterImpl.java | 1 - 5 files changed, 91 insertions(+), 32 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index 5d724b940b..b535dd0488 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -24,10 +24,12 @@ import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.WriteRequest; @@ -54,6 +56,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientProfile; @@ -92,7 +95,6 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LWM2M_STRATEGY_2; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.DISCOVER; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.LwM2mTypeOper.OBSERVE; @@ -294,23 +296,31 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler @Override public void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, Lwm2mClientRpcRequest rpcRequest) { if (response.getContent() != null) { + Object value = null; if (response.getContent() instanceof LwM2mObject) { LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); + if (rpcRequest != null) { + value = lwM2mObject.toString(); + } this.updateObjectResourceValue(registration, lwM2mObject, path); } else if (response.getContent() instanceof LwM2mObjectInstance) { LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) response.getContent(); + if (rpcRequest != null) { + value = lwM2mObjectInstance.toString(); + } this.updateObjectInstanceResourceValue(registration, lwM2mObjectInstance, path); } else if (response.getContent() instanceof LwM2mResource) { LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); if (rpcRequest != null) { - Object valueResp = lwM2mResource.isMultiInstances() ? lwM2mResource.getValues() : lwM2mResource.getValue(); - Object value = this.converter.convertValue(valueResp, lwM2mResource.getType(), ResourceModel.Type.STRING, - new LwM2mPath(convertPathFromIdVerToObjectId(path))); - rpcRequest.setValueMsg(String.format("%s", value)); - this.sentRpcRequest(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); + value = lwM2mResource.isMultiInstances() ? ((LwM2mMultipleResource) lwM2mResource).toString() : + ((LwM2mSingleResource) lwM2mResource).toString(); } this.updateResourcesValue(registration, lwM2mResource, path); } + if (rpcRequest != null) { + rpcRequest.setValueMsg(String.format("%s", value)); + this.sentRpcRequest(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); + } } } @@ -616,7 +626,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler public void sendLogsToThingsboard(String logMsg, String registrationId) { SessionInfoProto sessionInfo = this.getValidateSessionInfo(registrationId); if (logMsg != null && sessionInfo != null) { - if(logMsg.length() > 1024){ + if (logMsg.length() > 1024) { logMsg = logMsg.substring(0, 1024); } this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvLogyToThingsboard(logMsg), sessionInfo); @@ -656,10 +666,10 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler /** * @param registration - * @param lwM2mObject - - * @param path - + * @param pathIdVer - */ - private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String path) { - LwM2mPath pathIds = new LwM2mPath(path); + private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String pathIdVer) { + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); lwM2mObject.getInstances().forEach((instanceId, instance) -> { String pathInstance = pathIds.toString() + "/" + instanceId; this.updateObjectInstanceResourceValue(registration, instance, pathInstance); @@ -669,10 +679,10 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler /** * @param registration - * @param lwM2mObjectInstance - - * @param path - + * @param pathIdVer - */ - private void updateObjectInstanceResourceValue(Registration registration, LwM2mObjectInstance lwM2mObjectInstance, String path) { - LwM2mPath pathIds = new LwM2mPath(path); + private void updateObjectInstanceResourceValue(Registration registration, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer) { + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { String pathRez = pathIds.toString() + "/" + resourceId; this.updateResourcesValue(registration, resource, pathRez); @@ -782,8 +792,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler lwM2MClient.getPendingReadRequests().addAll(pathSend); ConcurrentHashMap finalParams = params; pathSend.forEach(target -> { - lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), - finalParams != null ? finalParams.get(target) : null, this.config.getTimeout(), null); + lwM2mTransportRequest.sendAllRequest(registration, target, typeOper, ContentFormat.TLV.getName(), + finalParams != null ? finalParams.get(target) : null, this.config.getTimeout(), null); }); if (OBSERVE.equals(typeOper)) { lwM2MClient.initReadValue(this, null); @@ -932,10 +942,9 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler public void onWriteResponseOk(Registration registration, String path, WriteRequest request) { if (request.getNode() instanceof LwM2mResource) { this.updateResourcesValue(registration, ((LwM2mResource) request.getNode()), path); - } - else if (request.getNode() instanceof LwM2mObjectInstance) { + } else if (request.getNode() instanceof LwM2mObjectInstance) { ((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> { - this.updateResourcesValue(registration, resource, path+ "/" + resId); + this.updateResourcesValue(registration, resource, path + "/" + resId); }); } @@ -1421,7 +1430,6 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } /** - * * @param lwM2MClient - */ public void updateFirmwareClient(LwM2mClient lwM2MClient) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java index a5780d9879..916c60765d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportHandlerUtil.java @@ -63,6 +63,13 @@ import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; import static org.eclipse.leshan.core.attributes.Attribute.MAXIMUM_PERIOD; import static org.eclipse.leshan.core.attributes.Attribute.MINIMUM_PERIOD; import static org.eclipse.leshan.core.attributes.Attribute.OBJECT_VERSION; +import static org.eclipse.leshan.core.model.ResourceModel.Type.BOOLEAN; +import static org.eclipse.leshan.core.model.ResourceModel.Type.FLOAT; +import static org.eclipse.leshan.core.model.ResourceModel.Type.INTEGER; +import static org.eclipse.leshan.core.model.ResourceModel.Type.OBJLNK; +import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; +import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING; +import static org.eclipse.leshan.core.model.ResourceModel.Type.TIME; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; @@ -508,4 +515,24 @@ public class LwM2mTransportHandlerUtil { return Sets.newConcurrentHashSet(attributeListOld); } + public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) { + switch (value.getClass().getSimpleName()) { + case "Double": + return FLOAT; + case "Integer": + return INTEGER; + case "String": + return STRING; + case "Boolean": + return BOOLEAN; + case "byte[]": + return OPAQUE; + case "Date": + return TIME; + case "ObjectLink": + return OBJLNK; + default: + return null; + } + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index e38823240b..9445fad379 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -60,6 +60,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.stream.Collectors; @@ -178,7 +179,7 @@ public class LwM2mTransportRequest { * send request: path = '/3/0' node == wM2mObjectInstance * with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} **/ - Collection resources = lwM2MClient.getNewResourcesForInstance( + Collection resources = lwM2MClient.getNewOneResourceForInstance( targetIdVer, params, this.config.getModelProvider(), this.converter); @@ -193,8 +194,14 @@ public class LwM2mTransportRequest { */ else if (resultIds.isObjectInstance()) { - String content = (String) params; -// node = Gson.fromJson((content, LwM2mNode.class); + if (((ConcurrentHashMap) params).size() > 0) { + Collection resources = lwM2MClient.getNewManyResourcesForInstance( + targetIdVer, params, + this.config.getModelProvider(), + this.converter); + request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), + resultIds.getObjectInstanceId(), resources); + } } else if (resultIds.getObjectId() >= 0) { request = new ObserveRequest(resultIds.getObjectId()); } @@ -264,9 +271,9 @@ public class LwM2mTransportRequest { * @param timeoutInMs - */ - @SuppressWarnings("unchecked") - private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { - + @SuppressWarnings({"error sendRequest"}) + private void sendRequest(Registration registration, LwM2mClient lwM2MClient, DownlinkRequest request, + long timeoutInMs, Lwm2mClientRpcRequest rpcRequest) { context.getServer().send(registration, request, timeoutInMs, (ResponseCallback) response -> { if (!lwM2MClient.isInit()) { lwM2MClient.initReadValue(this.serviceImpl, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java index 3e56cbb374..4fc319a588 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClient.java @@ -44,6 +44,7 @@ import java.util.stream.Collectors; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.TRANSPORT_DEFAULT_LWM2M_VERSION; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.equalsResourceTypeGetSimpleName; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandlerUtil.getVerFromPathIdVerOrId; @Slf4j @@ -103,23 +104,40 @@ public class LwM2mClient implements Cloneable { } } - public ResourceModel getResourceModel(String pathRez, LwM2mModelProvider modelProvider) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); + public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) { + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); - String verRez = getVerFromPathIdVerOrId(pathRez); + String verRez = getVerFromPathIdVerOrId(pathIdVer); return verRez == null || verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) .getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()) : null; } - public Collection getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, - LwM2mValueConverterImpl converter) { + public Collection getNewOneResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, + LwM2mValueConverterImpl converter) { LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources; resourceModels.forEach((resId, resourceModel) -> { if (resId == pathIds.getResourceId()) { - resources.add(LwM2mSingleResource.newResource(resId, converter.convertValue(params, ResourceModel.Type.STRING, resourceModel.type, pathIds), resourceModel.type)); + resources.add(LwM2mSingleResource.newResource(resId, converter.convertValue(params, + equalsResourceTypeGetSimpleName(params), resourceModel.type, pathIds), resourceModel.type)); + + }}); + return resources; + } + + public Collection getNewManyResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, + LwM2mValueConverterImpl converter) { + LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); + Collection resources = ConcurrentHashMap.newKeySet(); + Map resourceModels = modelProvider.getObjectModel(registration) + .getObjectModel(pathIds.getObjectId()).resources; + resourceModels.forEach((resId, resourceModel) -> { + if (((ConcurrentHashMap) params).containsKey(String.valueOf(resId))) { + Object value = ((ConcurrentHashMap) params).get((String.valueOf(resId))); + resources.add(LwM2mSingleResource.newResource(resId, + converter.convertValue(value, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type)); }}); return resources; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java index 3395fd7090..77a18af6bc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/utils/LwM2mValueConverterImpl.java @@ -177,7 +177,6 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { } default: } - throw new CodecException("Invalid value type for resource %s, expected %s, got %s", resourcePath, expectedType, currentType); } From 89c1743fa0549034767a45ee740f8f53114f0ead Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 5 May 2021 18:27:39 +0300 Subject: [PATCH 127/127] Lwm2m: RPC finish --- .../DefaultLwM2MTransportMsgHandler.java | 34 +++++++++++++++++-- .../lwm2m/server/LwM2mTransportRequest.java | 17 ++++++++-- .../server/client/Lwm2mClientRpcRequest.java | 10 ++++++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java index b9de7bcea2..a654abdad5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java @@ -104,6 +104,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @@ -455,9 +456,23 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler lwm2mClientRpcRequest.setValue(rpcRequest.get(lwm2mClientRpcRequest.valueKey).getAsString()); } if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonObject()) { - lwm2mClientRpcRequest.setParams(new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey) + ConcurrentHashMap params = new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey) .getAsJsonObject().toString(), new TypeToken>() { - }.getType())); + }.getType()); + if (WRITE_UPDATE == lwm2mClientRpcRequest.getTypeOper()) { + ConcurrentHashMap paramsResourceId = convertParamsToResourceId (params, sessionInfo); + if (paramsResourceId.size() > 0) { + lwm2mClientRpcRequest.setParams(paramsResourceId); + } + } + else { + lwm2mClientRpcRequest.setParams(params); + } + } + else if (rpcRequest.has(lwm2mClientRpcRequest.paramsKey) && rpcRequest.get(lwm2mClientRpcRequest.paramsKey).isJsonArray()) { + new Gson().fromJson(rpcRequest.get(lwm2mClientRpcRequest.paramsKey) + .getAsJsonObject().toString(), new TypeToken>() { + }.getType()); } lwm2mClientRpcRequest.setSessionInfo(sessionInfo); if (OBSERVE_READ_ALL != lwm2mClientRpcRequest.getTypeOper() && lwm2mClientRpcRequest.getTargetIdVer() == null) { @@ -484,6 +499,21 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler return lwm2mClientRpcRequest; } + private ConcurrentHashMap convertParamsToResourceId (ConcurrentHashMap params, + SessionInfoProto sessionInfo) { + ConcurrentHashMap paramsIdVer = new ConcurrentHashMap<>(); + params.forEach((k, v) -> { + String targetIdVer = this.getPresentPathIntoProfile(sessionInfo, k); + if (targetIdVer != null ) { + LwM2mPath targetId = new LwM2mPath(convertPathFromIdVerToObjectId(targetIdVer)); + if (targetId.isResource()) { + paramsIdVer.put(String.valueOf(targetId.getResourceId()), v); + } + } + }); + return paramsIdVer; + } + public void sentRpcRequest(Lwm2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { rpcRequest.setResponseCode(requestCode); if (LOG_LW2M_ERROR.equals(typeMsg)) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java index a187143974..0a5a545065 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java @@ -197,8 +197,18 @@ public class LwM2mTransportRequest { targetIdVer, params, this.config.getModelProvider(), this.converter); - request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), - resultIds.getObjectInstanceId(), resources); + if (resources.size()>0) { + request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), + resultIds.getObjectInstanceId(), resources); + } + else { + Lwm2mClientRpcRequest rpcRequestClone = (Lwm2mClientRpcRequest) rpcRequest.clone(); + if (rpcRequestClone != null) { + String errorMsg = String.format("Path %s params is not valid", targetIdVer); + serviceImpl.sentRpcRequest(rpcRequestClone, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); + rpcRequest = null; + } + } } } else if (resultIds.getObjectId() >= 0) { request = new ObserveRequest(resultIds.getObjectId()); @@ -218,7 +228,8 @@ public class LwM2mTransportRequest { } catch (ClientSleepingException e) { DownlinkRequest finalRequest = request; long finalTimeoutInMs = timeoutInMs; - lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, rpcRequest)); + Lwm2mClientRpcRequest finalRpcRequest = rpcRequest; + lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, finalRpcRequest)); } catch (Exception e) { log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java index 4734b5dd8e..0f0bd34024 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/Lwm2mClientRpcRequest.java @@ -107,4 +107,14 @@ public class Lwm2mClientRpcRequest { .setRequestId(this.requestId) .build(); } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + e.printStackTrace(); + } + return null; + } }