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/476] [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/476] 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 9a747d49b19822d1a9eb93bc04b418a4ed55eafa Mon Sep 17 00:00:00 2001 From: bishwasregmi Date: Thu, 18 Mar 2021 21:59:30 +0100 Subject: [PATCH 003/476] added example white label logo(of TU Delft) to dashboard toolbar --- .../dashboard-toolbar.component.html | 4 ++++ .../dashboard-toolbar.component.scss | 12 ++++++++++++ .../dashboard-toolbar.component.ts | 2 ++ ui-ngx/src/assets/white_label_logo.png | Bin 0 -> 36884 bytes ui-ngx/src/assets/white_label_logo.svg | 1 + 5 files changed, 19 insertions(+) create mode 100644 ui-ngx/src/assets/white_label_logo.png create mode 100644 ui-ngx/src/assets/white_label_logo.svg diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.html index de15c409ec..676fada4c3 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.html @@ -26,6 +26,10 @@ +
+ +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss index e54edd8815..a5f9d9ef3f 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.scss @@ -76,6 +76,18 @@ tb-dashboard-toolbar { height: $mobile-toolbar-height; min-height: $mobile-toolbar-height; } + & > div { + height: $toolbar-height; + .whitelabel-logo-db { + width: auto; + max-width: 100%; + height: 30px; + max-height: 100%; + margin-left: 20px; + margin-top: 5px; + margin-right: 30px; + } + } @media #{$mat-gt-sm} { height: $toolbar-height; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.ts index 201f70787a..176375acb3 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.ts @@ -43,4 +43,6 @@ export class DashboardToolbarComponent implements OnInit { this.triggerClick.emit(); } + white_label_logo = 'assets/white_label_logo.png'; + } diff --git a/ui-ngx/src/assets/white_label_logo.png b/ui-ngx/src/assets/white_label_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..da558f7b7b5a60aaeecd4d3aa4a6952b56b2b1ed GIT binary patch literal 36884 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 literal 0 HcmV?d00001 diff --git a/ui-ngx/src/assets/white_label_logo.svg b/ui-ngx/src/assets/white_label_logo.svg new file mode 100644 index 0000000000..706ca443f0 --- /dev/null +++ b/ui-ngx/src/assets/white_label_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file From 8f2438d6ab57ca22b940a001e9a05e68225e6c69 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Mon, 22 Mar 2021 17:17:42 +0200 Subject: [PATCH 004/476] 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 e7fe89f589e008a85cd628e7ed0ecc934b6e379b Mon Sep 17 00:00:00 2001 From: mdheer Date: Mon, 22 Mar 2021 22:21:25 +0100 Subject: [PATCH 005/476] Added dashboard logo checkbox --- .../dashboard-page.component.html | 32 ++++++++++++------ .../dashboard-page.component.scss | 11 +++++- .../dashboard-page.component.ts | 11 ++++++ .../dashboard-settings-dialog.component.html | 3 ++ .../dashboard-settings-dialog.component.ts | 1 + .../dashboard-toolbar.component.html | 4 --- .../home/pages/api-usage/api_usage_json.raw | 3 +- .../src/app/shared/models/dashboard.models.ts | 1 + ui-ngx/src/assets/example_logo.png | Bin 0 -> 34158 bytes .../assets/locale/locale.constant-en_US.json | 1 + ui-ngx/src/assets/white_label_logo.png | Bin 36884 -> 0 bytes ui-ngx/src/assets/white_label_logo.svg | 1 - 12 files changed, 50 insertions(+), 18 deletions(-) create mode 100644 ui-ngx/src/assets/example_logo.png delete mode 100644 ui-ngx/src/assets/white_label_logo.png delete mode 100644 ui-ngx/src/assets/white_label_logo.svg 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 f82a7a82ca..4fa2c9cc7a 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 @@ -46,16 +46,26 @@ view_compact - - + +
+ + + + + + +
@@ -286,4 +296,4 @@ -
+ \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss index c7d2367975..ed795e129e 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss @@ -54,6 +54,14 @@ div.tb-dashboard-page { z-index: 13; pointer-events: none; + .db-logo-and-title { + height: $toolbar-height; + .whitelabel-logo-db{ + height: 100%; + width: auto; + } + } + &.tb-dashboard-toolbar-opened { right: 0; // transition: right .3s cubic-bezier(.55, 0, .55, .2); @@ -63,8 +71,9 @@ div.tb-dashboard-page { right: 18px; transition: right .3s cubic-bezier(.55, 0, .55, .2) .2s; } - } + + } .tb-dashboard-container { &.tb-dashboard-toolbar-opened { &.is-fullscreen { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 5b8c7a17dc..2fc5dc98de 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -189,6 +189,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC currentDashboardScope: DashboardPageScope; addingLayoutCtx: DashboardPageLayoutContext; + + dashboard_logo = 'assets/example_logo.png'; dashboardCtx: DashboardContext = { @@ -480,6 +482,15 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC } } + public showDashboardLogo(): boolean { + if (this.dashboard.configuration.settings && + isDefined(this.dashboard.configuration.settings.showDashboardLogo)) { + return this.dashboard.configuration.settings.showDashboardLogo; + } else { + return true; + } + } + public showRightLayoutSwitch(): boolean { return this.isMobile && this.layouts.right.show; } 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 ede8052c68..89d4510c44 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 @@ -53,6 +53,9 @@
+ + {{ 'dashboard.display-dashboard-showDashboardLogo' | translate }} + {{ 'dashboard.display-dashboards-selection' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts index 36bbe80fa0..195f7e6433 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts @@ -79,6 +79,7 @@ export class DashboardSettingsDialogComponent extends DialogComponent -
- -
diff --git a/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw b/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw index b54ce8ea99..0a037e80bb 100644 --- a/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw +++ b/ui-ngx/src/app/modules/home/pages/api-usage/api_usage_json.raw @@ -4413,7 +4413,8 @@ "showDashboardExport": false, "toolbarAlwaysOpen": true, "titleColor": "rgba(0,0,0,0.870588)", - "showFilters": false + "showFilters": false, + "showDashboardLogo": false } }, "name": "Api Usage" diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index a92bc3d672..4b9fa0d582 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -86,6 +86,7 @@ export interface DashboardSettings { showDashboardsSelect?: boolean; showEntitiesSelect?: boolean; showFilters?: boolean; + showDashboardLogo?: boolean; showDashboardTimewindow?: boolean; showDashboardExport?: boolean; toolbarAlwaysOpen?: boolean; diff --git a/ui-ngx/src/assets/example_logo.png b/ui-ngx/src/assets/example_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..44cd0245616ead898328fe8c2549c8268d3d4a7a GIT binary patch literal 34158 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 literal 0 HcmV?d00001 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 f5d2990b5e..9d58a0a715 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -725,6 +725,7 @@ "display-filters": "Display filters", "display-dashboard-timewindow": "Display timewindow", "display-dashboard-export": "Display export", + "display-dashboard-showDashboardLogo": "Display dashboard logo", "import": "Import dashboard", "export": "Export dashboard", "export-failed-error": "Unable to export dashboard: {{error}}", diff --git a/ui-ngx/src/assets/white_label_logo.png b/ui-ngx/src/assets/white_label_logo.png deleted file mode 100644 index da558f7b7b5a60aaeecd4d3aa4a6952b56b2b1ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 36884 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 diff --git a/ui-ngx/src/assets/white_label_logo.svg b/ui-ngx/src/assets/white_label_logo.svg deleted file mode 100644 index 706ca443f0..0000000000 --- a/ui-ngx/src/assets/white_label_logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file From 0532d22d9dfeede30fd206b41ea1398629c654e5 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 24 Mar 2021 12:05:43 +0200 Subject: [PATCH 006/476] 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 007/476] 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 008/476] 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 009/476] 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 010/476] 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 011/476] 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 9b5ec2e5696d33b1234ea842bd21c240b79078e4 Mon Sep 17 00:00:00 2001 From: mdheer Date: Mon, 5 Apr 2021 17:45:01 +0200 Subject: [PATCH 012/476] logo is only shown in fullscreen --- .../components/dashboard-page/dashboard-page.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4fa2c9cc7a..59cdf01c2a 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 @@ -51,7 +51,7 @@ fxLayoutAlign.gt-sm="start center" fxLayoutAlign="end center" fxLayoutGap="12px" class="db-logo-and-title" > - Date: Tue, 6 Apr 2021 00:34:31 +0200 Subject: [PATCH 013/476] added functionality that lets you specify the path to the desired custom logo from the settings menu --- .../dashboard-page.component.ts | 22 ++++++++++++++---- .../dashboard-settings-dialog.component.html | 4 ++++ .../dashboard-settings-dialog.component.ts | 6 +++++ .../src/app/shared/models/dashboard.models.ts | 1 + .../assets/locale/locale.constant-en_US.json | 2 ++ ui-ngx/src/assets/nasa.png | Bin 0 -> 99886 bytes 6 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 ui-ngx/src/assets/nasa.png diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 2fc5dc98de..8671c7d195 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -120,6 +120,8 @@ import { DisplayWidgetTypesPanelData, WidgetTypes } from '@home/components/dashboard-page/widget-types-panel.component'; +import { LogoComponent } from '@app/shared/components/logo.component'; +import { concat } from 'lodash'; // @dynamic @Component({ @@ -189,10 +191,10 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC currentDashboardScope: DashboardPageScope; addingLayoutCtx: DashboardPageLayoutContext; - - dashboard_logo = 'assets/example_logo.png'; - + logo_name_local = 'example_logo.png'; + dashboard_logo = 'assets/' + this.logo_name_local; + dashboardCtx: DashboardContext = { instanceId: this.utils.guid(), getDashboard: () => this.dashboard, @@ -490,6 +492,11 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC return true; } } + + public updateLogoName(): string { + this.dashboard_logo = 'assets/' + this.dashboard.configuration.settings.logoName + return this.dashboard.configuration.settings.logoName; + } public showRightLayoutSwitch(): boolean { return this.isMobile && this.layouts.right.show; @@ -611,11 +618,16 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { settings: deepClone(this.dashboard.configuration.settings), - gridSettings + gridSettings, } - }).afterClosed().subscribe((data) => { + } + ).afterClosed().subscribe((data) => { if (data) { this.dashboard.configuration.settings = data.settings; + if (this.dashboard.configuration.settings) + { + this.updateLogoName(); + } const newGridSettings = data.gridSettings; if (newGridSettings) { const layout = this.dashboard.configuration.states[layoutKeys.state].layouts[layoutKeys.layout]; 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 89d4510c44..f745a8508d 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 @@ -51,6 +51,10 @@ openOnInput formControlName="titleColor"> + + dashboard.display-dashboard-enterLogoName + +
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts index 195f7e6433..9aae5c0aaf 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-settings-dialog.component.ts @@ -34,6 +34,7 @@ export interface DashboardSettingsDialogData { gridSettings?: GridSettings; } + @Component({ selector: 'tb-dashboard-settings-dialog', templateUrl: './dashboard-settings-dialog.component.html', @@ -45,13 +46,16 @@ export class DashboardSettingsDialogComponent extends DialogComponent, protected router: Router, @@ -69,6 +73,7 @@ export class DashboardSettingsDialogComponent extends DialogComponentjlmY<6&Kb?>BH({K`=}@< z4g5p=&S@)-2Y-U@4pEjtU&neuh>!kY>NgI48GuN?*Ych}T=MeP(thec9ZyZ2hEU~t zVaV*suqMz-_aNte#=2_1dd&UVyw6vAP-$1Jr_bWq^|j=JROl@;71j6O1=kl`*AGuW z?C}R^X;ZHPMRCU~2b;LB1CK{D{W{#nnh3zD|NmeAU!H*T=jZs~d=QOs7j?|jUI@E+ z{7{rKmj?8dX)q4L$KE^0S>0zz2&?EHpfwz%NtGV#8)6~uuV@}s2Xq9`T{o-<~Oyf~v)K&^I z0AP?~=>b4!if;g=BCBgJZzH_nhwP|P%_vMs5fUAAr~9us$Z@iaKn&tXd-Pa6VChAY z4~seEGp}D7sAuD-^o}fANmkVY7di7EVv+BQbWVy75`0A*jCC9;?Cp~6-RzXO_3IPV z-5v#qaJzPL3XY%+BsMKHEu(btM*@}66Y7>^xT6$4VPW7P%-;}$AgKvo-fn6nHh2y; zG`QCDu`kRU)F(|t#IN!sZv+{c4Ern;RT?~$8sY}829u5JR*%WQGJ%^RCqrWefU;=b z>b>y>SN(+vc?!DXdc!Zv%u#ISC+(J%}Q6v2o&wL8=P zOiwVeHwnKk%{LN%SzfW1gON%?F+;CADKuCaXSAYkJ}bb!(~{Rj!%z4E0K#aAz)({X zW=6P(DcsPQ?BbCy>|UcIN_Pp>igi}|FGg=pyYJH%a@Y?pJ1m?xMuohobkZmQ(2b8+ zWK7}pl#9z7Rbun$Uvz#E_50MNkp+6)@9~(w*A2jkl0H#<9FDs4{)Yf=DNEMn0xmzY-`w?UOsiO9+)7rpE%DXqYVhPfbbk>Bs8}CG$ko`n zI}HjI_T`y_S3HMO(BReF#_+~>PHUoeVW?mbCIELWp(ig-IrAQ#X=XV^SEy(!$t;mL z$8M8VpLWvVX>#1%GE0aF9w`K|25Kg12m1hhhK=GkIy(^C_skN}b9Cl473S7M&0;Ki zH$lRPVYL_#Tm1d|eovrkVM>?XI=JFBRp~cc;x6F*n(%93{TMr|b_SP_?PWD-S^&sl zR_{axP+_4_oSCrk=Lz-J!;H!rtfj#FDv3#d{n2;q)(XoHadzS0&>4(~p;zPL_8_c^ z$6G%SKj?EyPY#A6hqjWN^Zardu|^9|t7M{yKzvw>2(dj0Ix7+Vai;U<&|zLZcos|P zt5U$MTw>DRjyJWb7Q93mHE{ZA0jF&BCD?*TNF4k3Y0VK!H0ihfBy4UKnTM|Fs^{Cw z^=a||&{_Sz&xVDzt9n5fL`Mva*}pzRzNsgFEj+iC65fO@%bE~H2d0I;+=zABLiZGu zl#e&SW!f1hlgu?(Aa|-7S{^cpzpBMyhjLN8MFOa55WCmP1T8jP*z3yE$TOv)NlSxJ z63O}Vl|q^=ELBg9gj<`%kqt5KTU>BUotQQB)hpbkV*Ms&1#8fKC}!}!;L@i?-n0+K zV=Z6Kk4?JYNr9h5M{K|FL8Lg*+@n5j{yRo6X@hoO;KwcBwhvEXi9JYwgg3%~{~8+I zX^tA78L`?`CLS`;B2&rxTSw6+367D;V9Qz&q65GhA_T1dw7m>$jv?G?@2+m1lyAE| z1n_#uA=ni&3!K%}CzKgJ2WE&Ji8vD?0|F6B$^7*yL9hK_1&Xx2@tDDQnyltx^)bt= zpB8tqmC283!2L)PBlg3wuF+rTJZMC1?i4>AAq|8MzC6K&1`KO%`|kbC!%xRR1%lNc z1keHLhGo@$x}V1X!8jY`g(AnF0cIoBHIbI1WN4>!PIh+zx6;Uf#HUqyG~lh4(fpq& zffxhf|rKy?Eid$2t8)WBYHZVj8<{FqOJ5*Z1AOk&wx za`;*z0eG#9ACM$aYa|AE1wF+vdwa@c=U<1dYw1MgPqDi8 zGfZGHcW$u@h1iqxczBAj2I}Y1#XKdAI@H@hj|ooOp+*KUEK@6QriFIM%;n@s zCEj45sb`r71#NqtFOh!MsjHyauBkLzuHzKP;kY38l>^fu7_OZ~8mz||Yd@m>noz~l z4F~W(L(!wkkCZ9hlKI&L`$_Ig8BM4|M~thFc8R@o(%LP!k*J9-5;!;7+BexS_sqHM zE(6oPnS&uXFd%;Z?c9#2Hym$6JZQ?PlAqyr%L1uebb&X)d&Y>fpFFpFxe7q z!UiNI{O|s}>6?#(RwiuH9o5z|D!MMNn&t5|rM?h^wmVlm`mKF# zNhUxT-(;OmEJ-SRRz(K3{6-JLfT)sEgy?5Dn@uM_OoAcL%#wJh`3;NSyN_HpfW#(~ zqJyAT-(_wT`}N3ps>EA{7|@GQGfnBO{?!&t4WIy$ceeL-f}UD|8}v?g>&}!RfW%Gh z9-Ht@gx!m0}1FdTm3`gROyx)b(>!h54uG6exC3GY4j+^<)dQc z$(rmlpgTd?A?}d-*b+Lg;5!$e=*C^DHD}EPP<`LG7D-lHWoePB>4;ZCAwUZL(ICa8 zx|y69o&OyTSnE|_H}yRjw>Xl3)W|;OZSRaQq?Wx0PYQV}gQ0afZ+B&29|;l-PWpDp zME_hX&}@X;z{L*91(4WfQMP+biFNB8Wuoh zQ^n~k*8E2#;KN=R4QS`NrL3@-EvS#2_>6wVgg7|qL%2m54j2zgs5^)%$we9SztSQD zH@Npv6K+H`0M$i8LA^*Xoo-UWO*Uwb3piHC+a3j)9tbz(EqCktV<5vxYF0tPREiT0 z8j^Wc0mH^p*3R`<8+WV#K=pI3E-5;|l`VeyP&}}8IzDkK(!J~zVmkIruKbPo_$vNB zv60O~AQ)?&!7gj$nl@%)kt`w%1^66$w8T0_{F2vHr}$}SJbHxUxZ;plzQh(`0cA;X zd#6cs#!Ti<%%v|w06=BcH6XqI>I5z8zbacttJgk&g!2#RX<@yuA(!sMc-7e=-EooC zp6u8EbUBIf0g1$bsMOHuiC?Ylo4j0Bt&Ao}fXYK{VG}3m4x6G$;&&n66pLGAQs8rN z{QN$L9Fqw|_C&3Fz4#rrFe>^1`W(B3ML~1$h+Lx9>>fE@3_(#fgInt zD4l43v05dKb90_1(E84~AlkT&%|H&aZLwIk(Kd6FDSVNQ>8mtYkmgADwUcbs;8LUs zpW%U4G;_Zf{$H)-%mInA>ynDQX03AoyNxz3xY*U$tc|?&jR?ngvO&pO*%IKFcY`Z@ zMEycL^gg^f=Va2!!9n*1Gb#mBU;{a>0GNIB%ErRE%Au^dlhB+-Pu zEP%w{G8%KrOZe?=_!l6!Of3n%Ke+L#idN8-L`ZLRHv|pn$6L8h)r=nYQP2_|X?5Cua-sxhUy>7VUA|hPF7JSp z$xs2o?_A|&Q*SH(8@B;X#|7v-O)O|UT&tL^+hK# z-o!drAD`RpAq}*W8Z-dZ+mRi&e+{1|oEq`afQR%WKi9ks_EaZW^Zi8Y^pR&HpBuj; zL({$-61IfJe!}Y`Sh{~p{awrHM*2$tL*`ARqNUh3DrZ)-zTI$IfTWxs_5i1nIK_UvypPXCGGzwU#ElX zbI$q;3rSjp<+bU=eC76U6l@990r%eGn5N!=dKIE7K>EAq zXgns;OU^%AE>%}Wu}z9Yn_@^3J%0;}7B#xo6KRDbHf>)~DgoVT{2uoFP6lUh&3RNr zjPWEwZS?p}!k0gNmXM(xv~~A4aMh9QOBw4yqFPKWEHe5KomIa2QSewcU7V&ZO$IzK z?|ngbG3o%Nq9|kbz30eCc;U7_`7x>fx5ce|Ev@=hvdq@a27s1gNbThThdgicXIhRz1k@X^|9 zoZ3pw$7pbXwH~WFn*%PZnu3TxwKuL!8>S3h68?-x5-IY#=aMJ~>^>SJZfF3sz+jog z_d_mo!N8{o{(wX21^#7ZIuI0-nvvsOL9$K8-Gw$0dezbX#zZl*aK@ty^L z7jU=)E$>`fIU&JhF!^s2#W@M&84lvx83;!;b`^KB{Pz7BKyC=T`NUfW?n^7(!klL; zoo`#Rc*M(YZQU=BiU8;uQ0OD>2+FV+6Awwg?PML%S4|6%0{4-7F=4(wCAVU+X`pzD z1;lWO!D*a~5)61N3!CdVFi>aq{FD11Vf-G?&()AI6fA9ESVU&u_`C~rKMt`& z_XNZ2Skkg(<8<=N>!w?}{h)qn*yx<9UWu${Y+_R()j=i10*UIoe<|$bN1vEiw>9s^ zJ{fP&)LViV2-EwllgZr&XAZAc)nlO#Lj%MgPWw!J|K3f-E?7B?1!lVZ5_Tn5{7~ZP z)F&w8b!_Mz#I6pM*jVntsuJp*_Irv|(zXVNUQ6VK^pk_5X&Bw5h{;S`v4GdNN@-rO zVl-X2=t-fmUpCI^`w2<=FileTun}`Fe<~bVLYZ07K~xXkUddeIqqpH3D*fN4!8Z&5 zH8E=TW82u8lD=CuV+{%~Gqsq(EY5yO`%)U?&;~s1Fu*t9jMQPSHv`i3vx;=~yv#%+ zgOs+vT-~^8o~Ilp%zkt{dzRinIeEv1o@l6+xA;InF~~8I+xnJb@ZV{N8@58=?LWCQ z#X57RtO&&b)Yu_ci^JO6d)%dB5HILK$`MK|YTR?GIBzQW!|dHu2o{7U1*GI)A%a1= zESBFqd=#iylPQlwQ=l*UkMd~U&8Ldem0(8Im~+Q9OEX!CA462jLzb|+nuM}N|bMbWYA?j=Gz zkH%-SP|)VwLz9jaY8pk*O&Ek~?7npG2Ff+Wx#Z616HOi9Kl~r*#4&f63 zyH`BaiqoQ43hMHH+TQQzYPDcAs$U>DFc ztrljJad{SFeL>Oao^O`loe7bGB@{-yYfQAxIaZQ+B%$A=gVVh33zyv0I~w{Q+J;GZ zOS5yXi>eqZs5A_=89Pd}zPU!$+Xc0|dXnp;7eV&nl}|3o3*cdN1o$T@1mIA=#S?*! zMcN3K_}Nd@S`#^C>&VjrDq1eG>0^!uBhUmR53m69bP~N0-Ft?+os5L{4)d`uV^2Wc zRFPDPRARNj(`j3mOseB3=en5qmF>c@x%l5vmkP(He)sKK$xi(_YO&azgPuV1DS4Dm z%uH3T`8tpLj6LYS&e|}$o3&-o`{phX1Po3~2H^;K%6J=!(eK)t$VkX6>_rb_u@i5S z7ung#J}*7}`XY2chRYMQb!GZ^{H@KWd3RZr$L?0=v*eFOPd+blVFpBk_^SND0wXvg zTV`^B=lV9|HI!6$?|^jnzvmRs#f-v{OXkS7ra9Tx0|wo|B=C`h8;_`cc-ho3{n8Mp zgtg~u2Hk9g_PvgWR->np^wBt&!+CcrW2XrN#6a5G?q0bV@l)+ zUOk=PoY_*@empCfp{^ALJ=bt!jD*bYcOxNzbnBQ*ykK#}!8S`Sra-Hfy1G$=s zS*aD<3aKlaymUq&d$uv6rX^7a7-Ns7ib6JARg`HiWFXa&cC;%+WsQkVJ}bvWz@$kWlRY6#i){!;fPBX z>(qmiWpl=S176*U%{*iWRBbvj2WMOA6 z37lDcuCC#d;?_z(e{AOTEXL(Ya;Pg(4vtd7ce-c(>~%RmOzRtX%KQO?uar$|WH{9w zeHPibh!ej~-+76bh6pv5dM)xXDchDiH5R3{yye>doQM8O@{f&U02#CD2x zu*&nuKzdAmTNE9O=>v7bOnkj&+g?6pB3F|W(%akPxB1%8@vSUJOV!e%#=lL*D)bO^ zelZL{tF$MI;lO9APbINB)-?u6E&l9*b((yTB_>75lXAcsgIY2(j`!Mc3d%6M|Hxw+ zfrLL`Ri|)vJL0aNX%JnU#y|SNW(N`e>Eec&Gt_fU7Lc|Pd8aWcSb&sM#CGTT4J%?O zW}&EVWa1%lfrdUFTwa2q+E7=B^SgnaQ-6wCFMEzW;O)23Pl=muBhMkl+8YZ*O-o~@ z5Mcz!qejXz+RZud3e7G_c2_W``>NCryQ;WqON| zr=$OT%EAD0Sa8P;rJsp%`Er&nG%<_Qjo*p>bhV%M7*HAMtma%6*^wDEQ6&M8j|Lmt zwOl_|9FxP1*^_UtdrK2dGFuIBqYa7FeaOdH;Gp}HlVC7#%KCPt^A)MKx4_xb{G{;X zfVG|E^+b1H-i{e0RsFqu+Bc*ixlvN_b6J(&!)K9lrV||-(30;@XeNbD=tLj*)bD)A zr5|4@sOnfDjMINk>0CoUVl@6f_pn2fnRZ#3XN$Njfop~24Zo~;_U z4bA>#c_N8VwE^=y&YS7l+@N7iwh>iHVuQ8UO_ipVyJV)&xWQaHCJ4Es&O}E~bex@; z2bI5UJlj>K>iNU1K`J!inUuJvCFRug2Luyl<&!R?c12WCfrq&7d=2f1^zwZF@qn1E z^D-KXVxgzr(`1Vm;WNCqK@G2Aq=~miwNdu{X}*dFu6MGd3pFVhQrJ=$NC_-r0rt2o z_bbgW@9mmT>PIJs2iq2Ghm1**9Fp;Wi^!Y=-3wffd16{=e`O4PW9&8EI&Gaj?Ls5L#4OX@2l7wy_kZ$UulvG>eMf&uX z-jjfn6*zgPEK*=F)7h}8x(xCvYqXIDn3pdd>$%mWrjBm-NL~7?paM^tN7Fm;!%Bnb z&^=Ye8kLk{W5Ou{ows3&Kar8N(&mL`NbXY$=6RpCb!L5EZMF`YU>JjYd&u+5u67tQ zy5fFa&j2zecsdn2_ab9xGH!B?^v`p)6R+%iid>Ac6%;T!F+ao!+qsp+vHHDr=R|)6 z(r%LbB~e_B#J6IducJI{LN;U41SAxxqwLn*!{!OW?gzg7Auh zJXt_EZzU6mo`SRvhG|jAu*)czF1a&UgVWTLmhYHf-&Ys>>8uE$AQt8D6ZCs~0ebD* z4E}%-V;w8WRWUV?g0j_$s`mx60l$qt}J$AHuaRWw0r# z=}S*~_-XOx)%h9{5InFBQ{SK zdH#oqe6I?0W9TBi9?&MWJf0`2V#ZAth;du^0kfdmO z&;5EngMyVBUd5dfPjczw~{7)48?sUH9au?xYX~b=;&PEBf_?8uw5Tx`2(=`DMhe7~7S>mOaS7Po;5U%0I|wWj$7Bc+}rcxtoKBn)i19d z76y;b0;o%6)GEGfC;7cs5vMt|n0P0R%Ssj1TO3XL5q6H&%aXJ2y^#l2C*wN7Cj3!?=u=FH;ns^k(HjNGNs%t#A0T?PR z^9}AU_NeCtxA+`V{cDqAI~3Yl_^3%B>Ti;p8_KKrMxhqF3ooerd-PJT!C=IN&+}J& zXwty+V^r!}CMG@j;E;%IO+#S+GN!4K6|&g?!ejNL*PX~qNUJ9t*}QMD>YcQAIs#49 zw2H*|+w!9qaAfTHhzsf`xUVmD+X^WC3c=o~D*Z{!c*XJ0a0m*Ok)HliddF&IaX9jK z|2^1o4O|39wU1|B_~K3Dk?po4`$ya_9KIir);t0%TM^0he?U#y$oDAE4rUXGhJS@z zN6Y?n*l3~Rf-h;d&oT!XGxxu1=u(OX=0XYf9x$owNnVR?DN{Z&s#E2i@X%_eC7u1O zw{rhGLojkgRMido7?siU^i>sk%KN;ErtEjYWiypBXPiN^4f+~A+h^(2#xE(ho@U0$ zW-j+5+!owc0gT!fFSj87{S@m$rQ=*YpKt#<8#vpi6RxA&P4Oz1Z%Qf-^$!dG_74(c zp#P%(+1BFKW(#wqex9@Mu;}?WrDJ&6Mc|{G&CE|YI7_S9TXdY@ALdcV7ex#m()ieM zJy(u8{mBRE)cI@gfBN1pxvun^L_E*%4pHN`|Gdy*x8tqv$7Tl1m`}Dw&(}Tvt_n0U z+Mg=%(vh}jU$Hz9-#&qFldpCn;T3tEH&1{b-N3~q8XEFLe39Frp4m-~EkWHttum*& zgIk|0)tg%q%xjYR#Ur~nnZoq)%@OZ{_cg)$%e@XL>s>j%*|JXedNc9ckHKbXdw^{^ zD&QXr#Sj~K9=Z;T#n;vqJK#iP?AJ+j9WW4S3`1Tkqpep~%^qjvlkF1X=w!Wq7j$L4b)dCn$DF^CR>hg`pGE#o+a?^aq|V?u&cZmcpgD^eMs#ZBz*AMD z&#u}cMD2s7VYZeIL7TjWEJnq#;$G5;|qvDZIRs2shuQR1cB%JTfRI@xO z8Pz&8(^!ViH@u|5HSO~UWmmpDxWdB&q>=OaCK@*DCRI=vY&VGL(WQSgE8BTg@}@Z! z6jA?ZMssXD5UIUg4ZJd<%{DWWKx^tL?;1YL<+MB)2$qkmVul%CQ4Uda++%-udfP} zf#)gX(D)cFy}xGX?L(acs>F8?h-9X2zd>Q~qc#K9`pJk31vzkZ!SeFo<#|V2t6pqJ zbw)5i^<2+RocS+#vl7Vc$)mh?QL?h|_O~>Y9o`+oGdPert{AnEb^Y^ST(sbi;14{Y zLz9_aJnLnl@?Y{AwL!$RNlHfnsUyP~YAOH&r7tD2O(oMSA#RWQ1!|gTxs7qQ)9}b| zW)r6IBTrMgQ!G$|Ky?F#udCz5+ph(~m?#*gd!lI+mpdS;{HW^i9<*ORZ4}{=1I0l7 zq0MFN5aH)jhK(#N;U=Rp_CQ~a@cbg_>s=6=OrQ>bBMDrpYAorj!&*@3uA!8eUDR#wH5r$v?Syq zW|Jc{7xjERJ3XC~%U6Ksk%Y{ifZU=UnoJyf*F#i5jK?JE3_ z84QPxVNED|4}#nvwjV)9vnM?=)sNOa^UC-cy9ZHZiJ7M32NmDDtmnPbkN1i@$zo4B zG;*VKbBmuRT zLmHawKl(14;?hEQ=>^~^kxS?8E1k@u)z?+4^6V4k{#LMK*wblfaZfSeFCG+sRL`wEf>OIfyKvvF}Q8lu@%7orgB zFN3cfT$8<3I2HNdn4NS}KBq=4``2UN>H#&y2g64N0$09UfCbFtsI=0S`uvg>_4=pZ z9O%r@jRa%>Q!9oHx7ve~b3g|p#Lg#TT=%Z9yU4pxqko?{Lbv4iTpUL5{uU^Bs4Ix0 z4f&uYOf4I|@2f2?sB#&>om{!61(GT6sFf4a#*)WgP5)ghg( z)MJXa-XW{tRkSS=-@I|*$(DeQAI_^?5Eh(o;xL^PC!f4F#IapULNVyr$^ln|6{)nq zrz&K7ssP=G$7r?{2poy2b#|cxB|I#Tg47NdS*;Z<*>uquqR*Z+L^BOvi?WyFn>E;I z8tc#H&;2R^6;l$th<UT4Kod8W~jFmtj!cb-oXI;_o2;b_dBFYD1} zt2OI4y>cR~RpOD3`{@z>pFziMFx*Kn$BFN7rd{d_b>iuErW{!Alr(JUaO5@@zX?W#+$-A$3$TL*cD}Q+U ztEcFCq0h=5z0YOsI``OkOH?E7vf+|1g3{ud66_Z}<%i*i(;((n=RJc1Tkxma{kAF^ z^bauh3+y+2d^o9MoygxvcKLt|lqrOG%td|n@#0{J7*I=FF6yWrTY!>&nJW^6r@7_7 zvKY7E$masFd2Lp{$5f)mOSU*Alt~GX(>|HKyg&zD|4xX)$%N8``tgtXc902RGi^~+ z)I9<5KH+-r8I`S<38+hA-e;A8%~ro-WmvP#d9T{4E*g`G<$6Ru0Kug7!PB{2ySWJIO~w?u9@6$i6K2X7I2%^?p|xfx|9bZ!&7S_+Zo7!boWwvCUoV; zAX=J0##;#zrlZ+2rH!A4jI3hKwViGezw@Qpt%P+Bxfk5 zR4>-C^+%+v$s9NBPJJ1ZrI;Lemy;KC>6fRAh=Lz47ZhA3^QvxfkhpNX5ACWT9OMg<)p_W$gX)X!m_$d8f#w4lD8^bq3cR6RwY`xxlW=UoreLL|)-~%xY<@%x z1HjOoF0?K3S~&?!CmAIn0U8(M!c)@?J{r+&)`9A!4M#&WSE5aKW=>gGt)I?Q&r9Q1 zc)lxQ3|AO#Uns@3cKFS3&S%@dEQ0e6kod`%$n#G8pi*BN|FbX$Ok14hyt3P4V6`J) zdu*ubw|r7tOc@9+2q0nFm$~mMx=&b_*ok-oI-{`gc zyBpxCo#4g8KpoOD{>IJSCR?lZ3-!>i7_Yf*YT1r=p;=aN-21un5@xVCQUGxr!<8ZS z=v?4Rr0n6(TjZSebu#E^WV7a7$c{-u}^aim+7?U0EQ!THV_H*#m1??(FOy8{=f5w0=#Vr># zjSD$J6k>DMyZI|WuZvmr#vsQ4T2q|QI!zHMk!#bT5rnfK1eD+zlX|Frb^Mugj@bc+ zY71Xp2%GLlei6ja+_A|d-CK|H{C`pF@{3D~e%pEIs4s}O^X?2gkavO-W!+D(Nvv0k zy{%G6Iv@u4QU4xVAcS8xhrS5+O4I+rn@$7FfZW%o! zNvrY$5(D!eEmVdPvK&U+EKp)l$;!O1ZqEC*n{O#@b&;vH zNRTBdDJi-nyqF*)F4a6}P7I7*LdUw;Atv5$+dJ^K&7uS;nPQePhpOQ*G5zR$9pmu| z%B$_YUfhm}^6X0ZTIEllcpYCa{cs8Xj6!A}A@bdO^Uu(WE%jYW8u9T@rS2HC^w!I(I9Spx9NAx5%Df ztKyW)GJcfPW#d3ZX3TqDj7Z5arj@;>@D;}UZ&Ii)01Pfq_dxg(&0y{`Xi`-CvTbPn zwhLqepE#J4vSr8G^doJ%nam}nqclKniXc9sE|4KPdHZy|Hhs0pWyW;hYLDi#s|+bq zbM=QC39Me0Y(>NA9#tn3cN%o7*p)5HRtbN*REQaVhcPT0Rf6#tj|-sk)-y(6V*HqS zq%_AM$2V?nnp}@U<9ZK+`zS0~rxcRKI`ibvgSbr$P5a93RFrv5Pr*@14v1#7lLS=z zW-R7t43FQsraSt2r(Kb6QnSxc7uq-&`#7Q-dj-mYM6udSV&}95i`qA29ZJI;3o;0k zQnQHO(|i}yqmWnPi|Fx4%1@!BGM`YKxKi>pxqc4;9_G;i|EQd z3ElxzITa&)DJdzMxMQuC?`*Wxi(bP%4wIj!RNr-Av}b$Ly5%yk+`GkKOw;n?19Fd_ zxYXH*J6KAI^>f}v#hE;wHwiU*m1A)j8f@<(`a>wDx zWFGr8HJ4zMR)9^^mH9quCR-HS)nqM90vNLao^(O&tN3d>N_tGKX{T@3zw#V3 z*Affge_17u>X2&uH~se@&%}~aM-c)ht{X}O*A6n`c%nq=@2%Z!D@wwC5Mj| zldBYu|7n2;s$$sto-Hm?XJ70k7aK3If!)IFSVjbs*<@lCyDiUdG+?@pD|sfh3H#VU zg6O5WreWjX*E_s}7scK=iC}=zs{M!_U-wq6pDH-phdaxT)^tu6N*e!+`E8R<#jAGz zF9tjc6~A2(_)Nx1U4-Z^;4@1Gn3eoS>atTZ0 z!~)mzu&yTuM>!^PTEr)ZF8we#rR!{x3j8YfOIgNjs&-3;ye8er%Q3lU57v^CKMk{I ziA3+eB;3*g8!1E^a{Hwa6NIBKdkK0hi0qg&m_BgZXbbv=n0+wbI6#j;krh2mNH zak^L2CzV3lARuG&n1nK4&5<1(EN_uO5T9HQg=y{(^i3pQItczI4WQn0pS;=X-nuJ6 z6V${&4iW!?r1n1!E%{t9h_IyOZqVg!f7C9)X!zf)NI=X?V$g$8Hpd8kC!TJxPK9Gj zK|12k{&d+xMkOdxB7Ho@5*1#v!_qGyP~Kr&b^mXd?y!QV$;=P zwCNUGc7!$}sMfj;wiS6QN?mR!zaj1#_h_vJ-xmLl`WcD;Ak2H2Tb5rd;Z|1m1q9{g z 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 JiR{Cm{{iIW1R?+c literal 0 HcmV?d00001 From b51fee8740223f8bd6172c211d409f30b947ccd0 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Apr 2021 10:30:30 +0300 Subject: [PATCH 014/476] 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 015/476] 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 016/476] 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 017/476] 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 018/476] 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 019/476] 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 020/476] 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 021/476] 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 022/476] 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 2f07283876743dcdda6918483402feb1ff911a8d Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 14 Apr 2021 16:14:47 +0300 Subject: [PATCH 023/476] Removed upload of demo edge rule chains --- .../thingsboard/server/service/install/InstallScripts.java | 6 ------ 1 file changed, 6 deletions(-) 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 158cadc1a3..e2fb3ed657 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 @@ -259,18 +259,12 @@ public class InstallScripts { try { createDefaultRuleChains(tenantId); createDefaultRuleChain(tenantId, "Thermostat"); - loadEdgeDemoRuleChains(tenantId); } catch (Exception e) { log.error("Unable to load dashboard from json", e); throw new RuntimeException("Unable to load dashboard from json", e); } } - private void loadEdgeDemoRuleChains(TenantId tenantId) throws Exception { - Path edgeDemoRuleChainsDir = Paths.get(getDataDir(), JSON_DIR, DEMO_DIR, EDGE_MANAGEMENT, RULE_CHAINS_DIR); - loadRuleChainsFromPath(tenantId, edgeDemoRuleChainsDir); - } - public void createOAuth2Templates() throws Exception { Path oauth2ConfigTemplatesDir = Paths.get(getDataDir(), JSON_DIR, SYSTEM_DIR, OAUTH2_CONFIG_TEMPLATES_DIR); try (DirectoryStream dirStream = Files.newDirectoryStream(oauth2ConfigTemplatesDir, path -> path.toString().endsWith(JSON_EXT))) { From 206a3d386fd869f426b41954fb610107b424bc28 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 14 Apr 2021 16:25:17 +0300 Subject: [PATCH 024/476] Edge event controller test stabilization --- .../BaseEdgeEventControllerTest.java | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java index 5edde458ce..f7ed71f177 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java @@ -42,7 +42,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public class BaseEdgeEventControllerTest extends AbstractControllerTest { private Tenant savedTenant; - private TenantId tenantId; private User tenantAdmin; @Before @@ -52,7 +51,6 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest { Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); savedTenant = doPost("/api/tenant", tenant, Tenant.class); - tenantId = savedTenant.getId(); Assert.assertNotNull(savedTenant); tenantAdmin = new User(); @@ -63,6 +61,10 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest { tenantAdmin.setLastName("Downs"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + // sleep 1 seconds to avoid CREDENTIALS updated message for the user + // user credentials is going to be stored and updated event pushed to edge notification service + // while service will be processing this event edge could be already added and additional message will be pushed + Thread.sleep(1000); } @After @@ -75,7 +77,6 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest { @Test public void testGetEdgeEvents() throws Exception { - Thread.sleep(500); Edge edge = constructEdge("TestEdge", "default"); edge = doPost("/api/edge", edge, Edge.class); @@ -83,29 +84,31 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest { Device savedDevice = doPost("/api/device", device, Device.class); doPost("/api/edge/" + edge.getId().toString() + "/device/" + savedDevice.getId().toString(), Device.class); - Thread.sleep(500); Asset asset = constructAsset("TestAsset", "default"); Asset savedAsset = doPost("/api/asset", asset, Asset.class); doPost("/api/edge/" + edge.getId().toString() + "/asset/" + savedAsset.getId().toString(), Asset.class); - Thread.sleep(500); EntityRelation relation = new EntityRelation(savedAsset.getId(), savedDevice.getId(), EntityRelation.CONTAINS_TYPE); doPost("/api/relation", relation); - Thread.sleep(500); - List edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?", - new TypeReference>() { - }, new TimePageLink(4)).getData(); - - Assert.assertFalse(edgeEvents.isEmpty()); + // wait while edge event for the relation entity persisted to DB + Thread.sleep(100); + List edgeEvents; + int attempt = 1; + do { + edgeEvents = doGetTypedWithTimePageLink("/api/edge/" + edge.getId().toString() + "/events?", + new TypeReference>() {}, new TimePageLink(4)).getData(); + attempt++; + Thread.sleep(100); + } while (edgeEvents.size() != 4 || attempt < 5); Assert.assertEquals(4, edgeEvents.size()); - Assert.assertEquals(EdgeEventType.RULE_CHAIN, edgeEvents.get(0).getType()); - Assert.assertEquals(EdgeEventType.DEVICE, edgeEvents.get(1).getType()); - Assert.assertEquals(EdgeEventType.ASSET, edgeEvents.get(2).getType()); - Assert.assertEquals(EdgeEventType.RELATION, edgeEvents.get(3).getType()); + Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RULE_CHAIN.equals(ee.getType()))); + Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.DEVICE.equals(ee.getType()))); + Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.ASSET.equals(ee.getType()))); + Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RELATION.equals(ee.getType()))); } private Device constructDevice(String name, String type) { From ba1aa7a579edd79e7b66dcc9157fae3339af6399 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 14 Apr 2021 16:31:16 +0300 Subject: [PATCH 025/476] Base Edge test - refactoring and stability fixes. Device Profile processor null pointer exception fix is provision is not set --- .../DeviceProfileMsgConstructor.java | 8 +- .../thingsboard/server/edge/BaseEdgeTest.java | 424 +++++++++++------- .../server/edge/imitator/EdgeImitator.java | 65 ++- 3 files changed, 319 insertions(+), 178 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java index 316707b9ef..c3f5346170 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java @@ -40,8 +40,6 @@ public class DeviceProfileMsgConstructor { .setName(deviceProfile.getName()) .setDefault(deviceProfile.isDefault()) .setType(deviceProfile.getType().name()) - .setTransportType(deviceProfile.getTransportType().name()) - .setProvisionType(deviceProfile.getProvisionType().name()) .setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile.getProfileData()))); // TODO: voba - should this be always null at the moment?? // if (deviceProfile.getDefaultRuleChainId() != null) { @@ -54,6 +52,12 @@ public class DeviceProfileMsgConstructor { if (deviceProfile.getDescription() != null) { builder.setDescription(deviceProfile.getDescription()); } + if (deviceProfile.getTransportType() != null) { + builder.setTransportType(deviceProfile.getTransportType().name()); + } + if (deviceProfile.getProvisionType() != null) { + builder.setProvisionType(deviceProfile.getProvisionType().name()); + } if (deviceProfile.getProvisionDeviceKey() != null) { builder.setProvisionDeviceKey(deviceProfile.getProvisionDeviceKey()); } diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index 6622a744b5..238e01f741 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -32,10 +32,12 @@ import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Dashboard; 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.EntityView; import org.thingsboard.server.common.data.Tenant; @@ -45,6 +47,15 @@ import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.device.profile.AlarmCondition; +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; +import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; +import org.thingsboard.server.common.data.device.profile.AlarmRule; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -57,6 +68,9 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.EntityKeyValueType; +import org.thingsboard.server.common.data.query.FilterPredicateValue; +import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.rule.RuleChain; @@ -71,7 +85,6 @@ import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.edge.EdgeEventService; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.edge.imitator.EdgeImitator; import org.thingsboard.server.gen.edge.AlarmUpdateMsg; import org.thingsboard.server.gen.edge.AssetUpdateMsg; @@ -81,6 +94,7 @@ import org.thingsboard.server.gen.edge.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg; +import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.EdgeConfiguration; @@ -106,6 +120,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Random; +import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -114,6 +129,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @Slf4j abstract public class BaseEdgeTest extends AbstractControllerTest { + private static final String CUSTOM_DEVICE_PROFILE_NAME = "Thermostat"; + private Tenant savedTenant; private TenantId tenantId; private User tenantAdmin; @@ -145,11 +162,17 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { tenantAdmin.setLastName("Downs"); tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + // sleep 1 seconds to avoid CREDENTIALS updated message for the user + // user credentials is going to be stored and updated event pushed to edge notification service + // while service will be processing this event edge could be already added and additional message will be pushed + Thread.sleep(1000); + installation(); edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); - // should be less, but events from SyncEdgeService stack with events from controller. will be fixed in next releases - edgeImitator.expectMessageAmount(7); + // TODO: voba - should be less, but events from SyncEdgeService stack with events from controller. will be fixed in next releases + // so ideally sync process should check current edge queue and add only missing entities to the edge queue + edgeImitator.expectMessageAmount(10); edgeImitator.connect(); } @@ -163,23 +186,49 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { .andExpect(status().isOk()); } - @Test public void test() throws Exception { testReceivedInitialData(); + Assert.assertEquals(10, edgeImitator.getDownlinkMsgs().size()); + testDevices(); + Assert.assertEquals(14, edgeImitator.getDownlinkMsgs().size()); + testAssets(); + Assert.assertEquals(18, edgeImitator.getDownlinkMsgs().size()); + testRuleChains(); + Assert.assertEquals(21, edgeImitator.getDownlinkMsgs().size()); + testDashboards(); + Assert.assertEquals(24, edgeImitator.getDownlinkMsgs().size()); + testRelations(); + Assert.assertEquals(26, edgeImitator.getDownlinkMsgs().size()); + testAlarms(); + Assert.assertEquals(29, edgeImitator.getDownlinkMsgs().size()); + testEntityView(); + Assert.assertEquals(31, edgeImitator.getDownlinkMsgs().size()); + testCustomer(); + Assert.assertEquals(33, edgeImitator.getDownlinkMsgs().size()); + testWidgetsBundleAndWidgetType(); + Assert.assertEquals(37, edgeImitator.getDownlinkMsgs().size()); + testTimeseries(); + Assert.assertEquals(38, edgeImitator.getDownlinkMsgs().size()); + testAttributes(); + Assert.assertEquals(41, edgeImitator.getDownlinkMsgs().size()); + testSendMessagesToCloud(); + Assert.assertEquals(50, edgeImitator.getDownlinkMsgs().size()); + testRpcCall(); + Assert.assertEquals(51, edgeImitator.getDownlinkMsgs().size()); } private Device findDeviceByName(String deviceName) throws Exception { @@ -204,10 +253,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { return asset; } - private Device saveDevice(String deviceName) throws Exception { + private Device saveDevice(String deviceName, String type) throws Exception { Device device = new Device(); device.setName(deviceName); - device.setType("test"); + device.setType(type); return doPost("/api/device", device, Device.class); } @@ -233,7 +282,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceRpcCallMsg); @@ -243,7 +292,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testReceivedInitialData() throws Exception { log.info("Checking received data"); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); EdgeConfiguration configuration = edgeImitator.getConfiguration(); Assert.assertNotNull(configuration); @@ -253,9 +302,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { UserId userId = edgeImitator.getUserId(); Assert.assertNotNull(userId); - Optional optionalMsg1 = edgeImitator.findMessageByType(DeviceUpdateMsg.class); - Assert.assertTrue(optionalMsg1.isPresent()); - DeviceUpdateMsg deviceUpdateMsg = optionalMsg1.get(); + Optional deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class); + Assert.assertTrue(deviceUpdateMsgOpt.isPresent()); + DeviceUpdateMsg deviceUpdateMsg = deviceUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()); Device device = doGet("/api/device/" + deviceUUID.toString(), Device.class); @@ -264,9 +313,25 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { new TypeReference>() {}, new PageLink(100)).getData(); Assert.assertTrue(edgeDevices.contains(device)); - Optional optionalMsg2 = edgeImitator.findMessageByType(AssetUpdateMsg.class); - Assert.assertTrue(optionalMsg2.isPresent()); - AssetUpdateMsg assetUpdateMsg = optionalMsg2.get(); + List deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class); + Assert.assertEquals(3, deviceProfileUpdateMsgList.size()); + Optional deviceProfileUpdateMsgOpt = + deviceProfileUpdateMsgList.stream().filter(dfum -> CUSTOM_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny(); + Assert.assertTrue(deviceProfileUpdateMsgOpt.isPresent()); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + UUID deviceProfileUUID = new UUID(deviceProfileUpdateMsg.getIdMSB(), deviceProfileUpdateMsg.getIdLSB()); + DeviceProfile deviceProfile = doGet("/api/deviceProfile/" + deviceProfileUUID.toString(), DeviceProfile.class); + Assert.assertNotNull(deviceProfile); + Assert.assertNotNull(deviceProfile.getProfileData()); + Assert.assertNotNull(deviceProfile.getProfileData().getAlarms()); + Assert.assertNotNull(deviceProfile.getProfileData().getAlarms().get(0).getClearRule()); + + testAutoGeneratedCodeByProtobuf(deviceProfileUpdateMsg); + + Optional assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class); + Assert.assertTrue(assetUpdateMsgOpt.isPresent()); + AssetUpdateMsg assetUpdateMsg = assetUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB()); Asset asset = doGet("/api/asset/" + assetUUID.toString(), Asset.class); @@ -277,9 +342,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { testAutoGeneratedCodeByProtobuf(assetUpdateMsg); - Optional optionalMsg3 = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); - Assert.assertTrue(optionalMsg3.isPresent()); - RuleChainUpdateMsg ruleChainUpdateMsg = optionalMsg3.get(); + Optional ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); + Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent()); + RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType()); UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB()); RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID.toString(), RuleChain.class); @@ -296,13 +361,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testDevices() throws Exception { log.info("Testing devices"); - Device savedDevice = saveDevice("Edge Device 2"); - + // 1 edgeImitator.expectMessageAmount(1); + Device savedDevice = saveDevice("Edge Device 2", "Default"); doPost("/api/edge/" + edge.getId().getId().toString() + "/device/" + savedDevice.getId().getId().toString(), Device.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; @@ -312,11 +376,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName()); Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType()); + // 2 edgeImitator.expectMessageAmount(1); doDelete("/api/edge/" + edge.getId().getId().toString() + "/device/" + savedDevice.getId().getId().toString(), Device.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; @@ -324,11 +388,34 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); + // 3 edgeImitator.expectMessageAmount(1); doDelete("/api/device/" + savedDevice.getId().getId().toString()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); + // we should not get any message because device is not assigned to edge any more + Assert.assertFalse(edgeImitator.waitForMessages(1)); + // 4 + edgeImitator.expectMessageAmount(1); + savedDevice = saveDevice("Edge Device 3", "Default"); + doPost("/api/edge/" + edge.getId().getId().toString() + + "/device/" + savedDevice.getId().getId().toString(), Device.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); + Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); + Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName()); + Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType()); + + // 5 + edgeImitator.expectMessageAmount(1); + doDelete("/api/device/" + savedDevice.getId().getId().toString()) + .andExpect(status().isOk()); + // in this case we should get messages because device was assigned to edge + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; @@ -342,13 +429,13 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testAssets() throws Exception { log.info("Testing assets"); - Asset savedAsset = saveAsset("Edge Asset 2"); + // 1 edgeImitator.expectMessageAmount(1); + Asset savedAsset = saveAsset("Edge Asset 2"); doPost("/api/edge/" + edge.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); AssetUpdateMsg assetUpdateMsg = (AssetUpdateMsg) latestMessage; @@ -358,11 +445,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName()); Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType()); + // 2 edgeImitator.expectMessageAmount(1); doDelete("/api/edge/" + edge.getId().getId().toString() + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); assetUpdateMsg = (AssetUpdateMsg) latestMessage; @@ -370,11 +457,32 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits()); Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits()); + // 3 edgeImitator.expectMessageAmount(1); doDelete("/api/asset/" + savedAsset.getId().getId().toString()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + // 4 + edgeImitator.expectMessageAmount(1); + savedAsset = saveAsset("Edge Asset 3"); + doPost("/api/edge/" + edge.getId().getId().toString() + + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); + assetUpdateMsg = (AssetUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits()); + Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits()); + Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName()); + Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType()); + + // 5 + edgeImitator.expectMessageAmount(1); + doDelete("/api/asset/" + savedAsset.getId().getId().toString()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); assetUpdateMsg = (AssetUpdateMsg) latestMessage; @@ -387,21 +495,21 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testRuleChains() throws Exception { log.info("Testing RuleChains"); + + // 1 + edgeImitator.expectMessageAmount(1); RuleChain ruleChain = new RuleChain(); ruleChain.setName("Edge Test Rule Chain"); ruleChain.setType(RuleChainType.EDGE); RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); - createRuleChainMetadata(savedRuleChain); - - // Wait before rule chain metadata saved to database before rule chain is assigned to edge + // sleep 1 seconds to avoid ENTITY_UPDATED_RPC_MESSAGE for the rule chain + // rule chain metadata is going to be stored and updated event pushed to edge notification service + // while service will be processing this event assignment rule chain to edge will be completed if bad timing Thread.sleep(1000); - - edgeImitator.expectMessageAmount(1); doPost("/api/edge/" + edge.getId().getId().toString() + "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg); RuleChainUpdateMsg ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage; @@ -410,13 +518,14 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); Assert.assertEquals(ruleChainUpdateMsg.getName(), savedRuleChain.getName()); + // 2 testRuleChainMetadataRequestMsg(savedRuleChain.getId()); + // 3 edgeImitator.expectMessageAmount(1); doDelete("/api/edge/" + edge.getId().getId().toString() + "/ruleChain/" + savedRuleChain.getId().getId().toString(), RuleChain.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg); ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage; @@ -424,17 +533,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits()); Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); + // 4 edgeImitator.expectMessageAmount(1); doDelete("/api/ruleChain/" + savedRuleChain.getId().getId().toString()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); - - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof RuleChainUpdateMsg); - ruleChainUpdateMsg = (RuleChainUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType()); - Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits()); - Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); log.info("RuleChains tested successfully"); } @@ -452,8 +555,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); @@ -502,15 +605,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testDashboards() throws Exception { log.info("Testing Dashboards"); + + // 1 + edgeImitator.expectMessageAmount(1); Dashboard dashboard = new Dashboard(); dashboard.setTitle("Edge Test Dashboard"); Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); - - edgeImitator.expectMessageAmount(1); doPost("/api/edge/" + edge.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); DashboardUpdateMsg dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; @@ -518,25 +621,24 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits()); Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits()); Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName()); - testAutoGeneratedCodeByProtobuf(dashboardUpdateMsg); + // 2 edgeImitator.expectMessageAmount(1); savedDashboard.setTitle("Updated Edge Test Dashboard"); doPost("/api/dashboard", savedDashboard, Dashboard.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName()); + // 3 edgeImitator.expectMessageAmount(1); doDelete("/api/edge/" + edge.getId().getId().toString() + "/dashboard/" + savedDashboard.getId().getId().toString(), Dashboard.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; @@ -544,17 +646,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits()); Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits()); + // 4 edgeImitator.expectMessageAmount(1); doDelete("/api/dashboard/" + savedDashboard.getId().getId().toString()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); - - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); - dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); - Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits()); - Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); log.info("Dashboards tested successfully"); } @@ -562,19 +658,17 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testRelations() throws Exception { log.info("Testing Relations"); + // 1 + edgeImitator.expectMessageAmount(1); Device device = findDeviceByName("Edge Device 1"); Asset asset = findAssetByName("Edge Asset 1"); - EntityRelation relation = new EntityRelation(); relation.setType("test"); relation.setFrom(device.getId()); relation.setTo(asset.getId()); relation.setTypeGroup(RelationTypeGroup.COMMON); - - edgeImitator.expectMessageAmount(1); doPost("/api/relation", relation); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage; @@ -588,6 +682,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); + // 2 edgeImitator.expectMessageAmount(1); doDelete("/api/relation?" + "fromId=" + relation.getFrom().getId().toString() + @@ -597,8 +692,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { "&toId=" + relation.getTo().getId().toString() + "&toType=" + relation.getTo().getEntityType().name()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); relationUpdateMsg = (RelationUpdateMsg) latestMessage; @@ -617,18 +711,17 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testAlarms() throws Exception { log.info("Testing Alarms"); - Device device = findDeviceByName("Edge Device 1"); + // 1 + edgeImitator.expectMessageAmount(1); + Device device = findDeviceByName("Edge Device 1"); Alarm alarm = new Alarm(); alarm.setOriginator(device.getId()); alarm.setStatus(AlarmStatus.ACTIVE_UNACK); alarm.setType("alarm"); alarm.setSeverity(AlarmSeverity.CRITICAL); - - edgeImitator.expectMessageAmount(1); Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; @@ -639,10 +732,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(alarmUpdateMsg.getStatus(), savedAlarm.getStatus().name()); Assert.assertEquals(alarmUpdateMsg.getSeverity(), savedAlarm.getSeverity().name()); + // 2 edgeImitator.expectMessageAmount(1); doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/ack"); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; @@ -652,10 +745,10 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.ACTIVE_ACK.name()); + // 3 edgeImitator.expectMessageAmount(1); doPost("/api/alarm/" + savedAlarm.getId().getId().toString() + "/clear"); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; @@ -665,26 +758,29 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name()); + // 4 + edgeImitator.expectMessageAmount(1); doDelete("/api/alarm/" + savedAlarm.getId().getId().toString()) .andExpect(status().isOk()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + log.info("Alarms tested successfully"); } private void testEntityView() throws Exception { log.info("Testing EntityView"); - Device device = findDeviceByName("Edge Device 1"); + // 1 + edgeImitator.expectMessageAmount(1); + Device device = findDeviceByName("Edge Device 1"); EntityView entityView = new EntityView(); entityView.setName("Edge EntityView 1"); entityView.setType("test"); entityView.setEntityId(device.getId()); EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class); - - edgeImitator.expectMessageAmount(1); doPost("/api/edge/" + edge.getId().getId().toString() + "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); EntityViewUpdateMsg entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; @@ -697,11 +793,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(entityViewUpdateMsg.getEntityIdLSB(), device.getUuidId().getLeastSignificantBits()); Assert.assertEquals(entityViewUpdateMsg.getEntityType().name(), device.getId().getEntityType().name()); + // 2 edgeImitator.expectMessageAmount(1); doDelete("/api/edge/" + edge.getId().getId().toString() + "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; @@ -709,17 +805,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits()); Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits()); + // 3 edgeImitator.expectMessageAmount(1); doDelete("/api/entityView/" + savedEntityView.getId().getId().toString()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); - - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); - entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); - Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits()); - Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); log.info("EntityView tested successfully"); } @@ -727,15 +817,14 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testCustomer() throws Exception { log.info("Testing Customer"); + // 1 + edgeImitator.expectMessageAmount(1); Customer customer = new Customer(); customer.setTitle("Edge Customer 1"); Customer savedCustomer = doPost("/api/customer", customer, Customer.class); - - edgeImitator.expectMessageAmount(1); doPost("/api/customer/" + savedCustomer.getId().getId().toString() + "/edge/" + edge.getId().getId().toString(), Edge.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); CustomerUpdateMsg customerUpdateMsg = (CustomerUpdateMsg) latestMessage; @@ -743,13 +832,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); Assert.assertEquals(customerUpdateMsg.getTitle(), savedCustomer.getTitle()); - testAutoGeneratedCodeByProtobuf(customerUpdateMsg); + // 2 edgeImitator.expectMessageAmount(1); doDelete("/api/customer/edge/" + edge.getId().getId().toString(), Edge.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); customerUpdateMsg = (CustomerUpdateMsg) latestMessage; @@ -757,17 +845,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); + // 3 edgeImitator.expectMessageAmount(1); doDelete("/api/customer/" + savedCustomer.getId().getId().toString()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); - - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); - customerUpdateMsg = (CustomerUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); - Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); - Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); log.info("Customer tested successfully"); } @@ -775,13 +857,12 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testWidgetsBundleAndWidgetType() throws Exception { log.info("Testing WidgetsBundle and WidgetType"); + // 1 + edgeImitator.expectMessageAmount(1); WidgetsBundle widgetsBundle = new WidgetsBundle(); widgetsBundle.setTitle("Test Widget Bundle"); - - edgeImitator.expectMessageAmount(1); WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; @@ -790,20 +871,18 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits()); Assert.assertEquals(widgetsBundleUpdateMsg.getAlias(), savedWidgetsBundle.getAlias()); Assert.assertEquals(widgetsBundleUpdateMsg.getTitle(), savedWidgetsBundle.getTitle()); - testAutoGeneratedCodeByProtobuf(widgetsBundleUpdateMsg); + // 2 + edgeImitator.expectMessageAmount(1); WidgetType widgetType = new WidgetType(); widgetType.setName("Test Widget Type"); widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); ObjectNode descriptor = mapper.createObjectNode(); descriptor.put("key", "value"); widgetType.setDescriptor(descriptor); - - edgeImitator.expectMessageAmount(1); WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); WidgetTypeUpdateMsg widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; @@ -814,11 +893,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(widgetTypeUpdateMsg.getName(), savedWidgetType.getName()); Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor()); + // 3 edgeImitator.expectMessageAmount(1); doDelete("/api/widgetType/" + savedWidgetType.getId().getId().toString()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; @@ -826,11 +905,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { Assert.assertEquals(widgetTypeUpdateMsg.getIdMSB(), savedWidgetType.getUuidId().getMostSignificantBits()); Assert.assertEquals(widgetTypeUpdateMsg.getIdLSB(), savedWidgetType.getUuidId().getLeastSignificantBits()); + // 4 edgeImitator.expectMessageAmount(1); doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getId().getId().toString()) .andExpect(status().isOk()); - edgeImitator.waitForMessages(); - + Assert.assertTrue(edgeImitator.waitForMessages()); latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; @@ -843,15 +922,15 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void testTimeseries() throws Exception { log.info("Testing timeseries"); - Device device = findDeviceByName("Edge Device 1"); + edgeImitator.expectMessageAmount(1); + Device device = findDeviceByName("Edge Device 1"); String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); - EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); - edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent1); + EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); + edgeEventService.saveAsync(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof EntityDataProto); @@ -890,7 +969,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof EntityDataProto); @@ -916,7 +995,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof EntityDataProto); @@ -941,7 +1020,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectMessageAmount(1); edgeEventService.saveAsync(edgeEvent1); clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof EntityDataProto); @@ -996,8 +1075,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); @@ -1013,7 +1092,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void sendDeviceWithNameThatAlreadyExistsOnCloud() throws Exception { String deviceOnCloudName = RandomStringUtils.randomAlphanumeric(15); - Device deviceOnCloud = saveDevice(deviceOnCloudName); + Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default"); UUID uuid = Uuids.timeBased(); @@ -1033,8 +1112,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); @@ -1063,7 +1142,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectMessageAmount(1); doPost("/api/relation", relation); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); RelationRequestMsg.Builder relationRequestMsgBuilder = RelationRequestMsg.newBuilder(); @@ -1078,8 +1157,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); @@ -1115,7 +1194,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); + Assert.assertTrue(edgeImitator.waitForResponses()); List alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?", @@ -1158,7 +1237,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); + Assert.assertTrue(edgeImitator.waitForResponses()); EntityRelation relation = doGet("/api/relation?" + "&fromId=" + device2.getId().getId().toString() + @@ -1212,7 +1291,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder2); edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build()); - edgeImitator.waitForResponses(); + Assert.assertTrue(edgeImitator.waitForResponses()); // Wait before device attributes saved to database before requesting them from controller Thread.sleep(1000); @@ -1243,8 +1322,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages());; AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); @@ -1270,8 +1349,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); @@ -1299,8 +1378,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg); @@ -1327,7 +1406,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); + Assert.assertTrue(edgeImitator.waitForResponses()); } private void sendDeviceRpcResponse() throws Exception { @@ -1352,7 +1431,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); + Assert.assertTrue(edgeImitator.waitForResponses()); } private void sendAttributesRequest() throws Exception { @@ -1383,8 +1462,8 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.expectMessageAmount(1); edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - edgeImitator.waitForResponses(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Assert.assertTrue(latestMessage instanceof EntityDataProto); @@ -1416,7 +1495,7 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { edgeImitator.expectResponsesAmount(1); edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build()); - edgeImitator.waitForResponses(); + Assert.assertTrue(edgeImitator.waitForResponses()); device = doGet("/api/device/" + device.getId().getId().toString(), Device.class); Assert.assertNotNull(device); List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getId().getId().toString() + "/devices?", @@ -1428,7 +1507,11 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { private void installation() throws Exception { edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); - Device savedDevice = saveDevice("Edge Device 1"); + DeviceProfile deviceProfile = this.createDeviceProfile(CUSTOM_DEVICE_PROFILE_NAME, null); + extendDeviceProfileData(deviceProfile); + doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + + Device savedDevice = saveDevice("Edge Device 1", CUSTOM_DEVICE_PROFILE_NAME); doPost("/api/edge/" + edge.getId().getId().toString() + "/device/" + savedDevice.getId().getId().toString(), Device.class); @@ -1437,6 +1520,35 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { + "/asset/" + savedAsset.getId().getId().toString(), Asset.class); } + private void extendDeviceProfileData(DeviceProfile deviceProfile) { + DeviceProfileData profileData = deviceProfile.getProfileData(); + List alarms = new ArrayList<>(); + DeviceProfileAlarm deviceProfileAlarm = new DeviceProfileAlarm(); + deviceProfileAlarm.setAlarmType("High Temperature"); + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setAlarmDetails("Alarm Details"); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setSpec(new SimpleAlarmConditionSpec()); + List condition = new ArrayList<>(); + AlarmConditionFilter alarmConditionFilter = new AlarmConditionFilter(); + alarmConditionFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperature")); + NumericFilterPredicate predicate = new NumericFilterPredicate(); + predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + predicate.setValue(new FilterPredicateValue<>(55.0)); + alarmConditionFilter.setPredicate(predicate); + alarmConditionFilter.setValueType(EntityKeyValueType.NUMERIC); + condition.add(alarmConditionFilter); + alarmCondition.setCondition(condition); + alarmRule.setCondition(alarmCondition); + deviceProfileAlarm.setClearRule(alarmRule); + TreeMap createRules = new TreeMap<>(); + createRules.put(AlarmSeverity.CRITICAL, alarmRule); + deviceProfileAlarm.setCreateRules(createRules); + alarms.add(deviceProfileAlarm); + profileData.setAlarms(alarms); + profileData.setProvisionConfiguration(new AllowCreateNewDevicesDeviceProfileProvisionConfiguration("123")); + } + private EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) { EdgeEvent edgeEvent = new EdgeEvent(); edgeEvent.setEdgeId(edgeId); diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java index e630f2b212..6b4234f151 100644 --- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java +++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java @@ -32,6 +32,7 @@ import org.thingsboard.server.gen.edge.CustomerUpdateMsg; import org.thingsboard.server.gen.edge.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.DeviceCredentialsUpdateMsg; +import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.DownlinkMsg; @@ -58,6 +59,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; @Slf4j public class EdgeImitator { @@ -154,88 +156,93 @@ public class EdgeImitator { private ListenableFuture> processDownlinkMsg(DownlinkMsg downlinkMsg) { List> result = new ArrayList<>(); - if (downlinkMsg.getDeviceUpdateMsgList() != null && !downlinkMsg.getDeviceUpdateMsgList().isEmpty()) { + if (downlinkMsg.getDeviceUpdateMsgCount() > 0) { for (DeviceUpdateMsg deviceUpdateMsg: downlinkMsg.getDeviceUpdateMsgList()) { result.add(saveDownlinkMsg(deviceUpdateMsg)); } } - if (downlinkMsg.getDeviceCredentialsUpdateMsgList() != null && !downlinkMsg.getDeviceCredentialsUpdateMsgList().isEmpty()) { + if (downlinkMsg.getDeviceProfileUpdateMsgCount() > 0) { + for (DeviceProfileUpdateMsg deviceProfileUpdateMsg : downlinkMsg.getDeviceProfileUpdateMsgList()) { + result.add(saveDownlinkMsg(deviceProfileUpdateMsg)); + } + } + if (downlinkMsg.getDeviceCredentialsUpdateMsgCount() > 0) { for (DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg: downlinkMsg.getDeviceCredentialsUpdateMsgList()) { result.add(saveDownlinkMsg(deviceCredentialsUpdateMsg)); } } - if (downlinkMsg.getAssetUpdateMsgList() != null && !downlinkMsg.getAssetUpdateMsgList().isEmpty()) { + if (downlinkMsg.getAssetUpdateMsgCount() > 0) { for (AssetUpdateMsg assetUpdateMsg: downlinkMsg.getAssetUpdateMsgList()) { result.add(saveDownlinkMsg(assetUpdateMsg)); } } - if (downlinkMsg.getRuleChainUpdateMsgList() != null && !downlinkMsg.getRuleChainUpdateMsgList().isEmpty()) { + if (downlinkMsg.getRuleChainUpdateMsgCount() > 0) { for (RuleChainUpdateMsg ruleChainUpdateMsg: downlinkMsg.getRuleChainUpdateMsgList()) { result.add(saveDownlinkMsg(ruleChainUpdateMsg)); } } - if (downlinkMsg.getRuleChainMetadataUpdateMsgList() != null && !downlinkMsg.getRuleChainMetadataUpdateMsgList().isEmpty()) { + if (downlinkMsg.getRuleChainMetadataUpdateMsgCount() > 0) { for (RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg: downlinkMsg.getRuleChainMetadataUpdateMsgList()) { result.add(saveDownlinkMsg(ruleChainMetadataUpdateMsg)); } } - if (downlinkMsg.getDashboardUpdateMsgList() != null && !downlinkMsg.getDashboardUpdateMsgList().isEmpty()) { + if (downlinkMsg.getDashboardUpdateMsgCount() > 0) { for (DashboardUpdateMsg dashboardUpdateMsg: downlinkMsg.getDashboardUpdateMsgList()) { result.add(saveDownlinkMsg(dashboardUpdateMsg)); } } - if (downlinkMsg.getRelationUpdateMsgList() != null && !downlinkMsg.getRelationUpdateMsgList().isEmpty()) { + if (downlinkMsg.getRelationUpdateMsgCount() > 0) { for (RelationUpdateMsg relationUpdateMsg: downlinkMsg.getRelationUpdateMsgList()) { result.add(saveDownlinkMsg(relationUpdateMsg)); } } - if (downlinkMsg.getAlarmUpdateMsgList() != null && !downlinkMsg.getAlarmUpdateMsgList().isEmpty()) { + if (downlinkMsg.getAlarmUpdateMsgCount() > 0) { for (AlarmUpdateMsg alarmUpdateMsg: downlinkMsg.getAlarmUpdateMsgList()) { result.add(saveDownlinkMsg(alarmUpdateMsg)); } } - if (downlinkMsg.getEntityDataList() != null && !downlinkMsg.getEntityDataList().isEmpty()) { + if (downlinkMsg.getEntityDataCount() > 0) { for (EntityDataProto entityData: downlinkMsg.getEntityDataList()) { result.add(saveDownlinkMsg(entityData)); } } - if (downlinkMsg.getEntityViewUpdateMsgList() != null && !downlinkMsg.getEntityViewUpdateMsgList().isEmpty()) { + if (downlinkMsg.getEntityViewUpdateMsgCount() > 0) { for (EntityViewUpdateMsg entityViewUpdateMsg: downlinkMsg.getEntityViewUpdateMsgList()) { result.add(saveDownlinkMsg(entityViewUpdateMsg)); } } - if (downlinkMsg.getCustomerUpdateMsgList() != null && !downlinkMsg.getCustomerUpdateMsgList().isEmpty()) { + if (downlinkMsg.getCustomerUpdateMsgCount() > 0) { for (CustomerUpdateMsg customerUpdateMsg: downlinkMsg.getCustomerUpdateMsgList()) { result.add(saveDownlinkMsg(customerUpdateMsg)); } } - if (downlinkMsg.getWidgetsBundleUpdateMsgList() != null && !downlinkMsg.getWidgetsBundleUpdateMsgList().isEmpty()) { + if (downlinkMsg.getWidgetsBundleUpdateMsgCount() > 0) { for (WidgetsBundleUpdateMsg widgetsBundleUpdateMsg: downlinkMsg.getWidgetsBundleUpdateMsgList()) { result.add(saveDownlinkMsg(widgetsBundleUpdateMsg)); } } - if (downlinkMsg.getWidgetTypeUpdateMsgList() != null && !downlinkMsg.getWidgetTypeUpdateMsgList().isEmpty()) { + if (downlinkMsg.getWidgetTypeUpdateMsgCount() > 0) { for (WidgetTypeUpdateMsg widgetTypeUpdateMsg: downlinkMsg.getWidgetTypeUpdateMsgList()) { result.add(saveDownlinkMsg(widgetTypeUpdateMsg)); } } - if (downlinkMsg.getUserUpdateMsgList() != null && !downlinkMsg.getUserUpdateMsgList().isEmpty()) { + if (downlinkMsg.getUserUpdateMsgCount() > 0) { for (UserUpdateMsg userUpdateMsg: downlinkMsg.getUserUpdateMsgList()) { onUserUpdate(userUpdateMsg); result.add(saveDownlinkMsg(userUpdateMsg)); } } - if (downlinkMsg.getUserCredentialsUpdateMsgList() != null && !downlinkMsg.getUserCredentialsUpdateMsgList().isEmpty()) { + if (downlinkMsg.getUserCredentialsUpdateMsgCount() > 0) { for (UserCredentialsUpdateMsg userCredentialsUpdateMsg: downlinkMsg.getUserCredentialsUpdateMsgList()) { result.add(saveDownlinkMsg(userCredentialsUpdateMsg)); } } - if (downlinkMsg.getDeviceRpcCallMsgList() != null && !downlinkMsg.getDeviceRpcCallMsgList().isEmpty()) { + if (downlinkMsg.getDeviceRpcCallMsgCount() > 0) { for (DeviceRpcCallMsg deviceRpcCallMsg: downlinkMsg.getDeviceRpcCallMsgList()) { result.add(saveDownlinkMsg(deviceRpcCallMsg)); } } - if (downlinkMsg.getDeviceCredentialsRequestMsgList() != null && !downlinkMsg.getDeviceCredentialsRequestMsgList().isEmpty()) { + if (downlinkMsg.getDeviceCredentialsRequestMsgCount() > 0) { for (DeviceCredentialsRequestMsg deviceCredentialsRequestMsg: downlinkMsg.getDeviceCredentialsRequestMsgList()) { result.add(saveDownlinkMsg(deviceCredentialsRequestMsg)); } @@ -256,15 +263,21 @@ public class EdgeImitator { return Futures.immediateFuture(null); } - public void waitForMessages() throws InterruptedException { - messagesLatch.await(5, TimeUnit.SECONDS); + public boolean waitForMessages() throws InterruptedException { + return waitForMessages(5); + } + + public boolean waitForMessages(int timeout) throws InterruptedException { + return messagesLatch.await(timeout, TimeUnit.SECONDS); } public void expectMessageAmount(int messageAmount) { messagesLatch = new CountDownLatch(messageAmount); } - public void waitForResponses() throws InterruptedException { responsesLatch.await(5, TimeUnit.SECONDS); } + public boolean waitForResponses() throws InterruptedException { + return responsesLatch.await(5, TimeUnit.SECONDS); + } public void expectResponsesAmount(int messageAmount) { responsesLatch = new CountDownLatch(messageAmount); @@ -282,6 +295,18 @@ public class EdgeImitator { return result; } + @SuppressWarnings("unchecked") + public List findAllMessagesByType(Class tClass) { + List result; + try { + lock.lock(); + result = (List) downlinkMsgs.stream().filter(downlinkMsg -> downlinkMsg.getClass().isAssignableFrom(tClass)).collect(Collectors.toList()); + } finally { + lock.unlock(); + } + return result; + } + public AbstractMessage getLatestMessage() { return downlinkMsgs.get(downlinkMsgs.size() - 1); } From d6f451ccca90ce6cd7c56d46f3b75ab804680b5e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 14 Apr 2021 16:36:34 +0300 Subject: [PATCH 026/476] 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 dbbb8e3aad419f2670cf47d95714628b03026716 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 14 Apr 2021 17:57:10 +0300 Subject: [PATCH 027/476] Added support for device profile update msg --- .../server/controller/BaseEdgeControllerTest.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java index ba2d3ce61e..cc50cdd00d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.edge.imitator.EdgeImitator; import org.thingsboard.server.gen.edge.AssetUpdateMsg; +import org.thingsboard.server.gen.edge.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.UserCredentialsUpdateMsg; @@ -672,22 +673,24 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { edgeImitator.ignoreType(UserCredentialsUpdateMsg.class); edgeImitator.expectMessageAmount(7); edgeImitator.connect(); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertEquals(7, edgeImitator.getDownlinkMsgs().size()); Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent()); + Assert.assertTrue(edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent()); edgeImitator.getDownlinkMsgs().clear(); - edgeImitator.expectMessageAmount(4); + edgeImitator.expectMessageAmount(7); doPost("/api/edge/sync/" + edge.getId()); - edgeImitator.waitForMessages(); + Assert.assertTrue(edgeImitator.waitForMessages()); - Assert.assertEquals(4, edgeImitator.getDownlinkMsgs().size()); + Assert.assertEquals(7, edgeImitator.getDownlinkMsgs().size()); Assert.assertTrue(edgeImitator.findMessageByType(RuleChainUpdateMsg.class).isPresent()); + Assert.assertTrue(edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(DeviceUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(AssetUpdateMsg.class).isPresent()); Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent()); From 0e9f8e91bc9b6e8fac9e7ce6a8b4350a9359c487 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 15 Apr 2021 10:20:40 +0300 Subject: [PATCH 028/476] Refactored expected msg count --- .../thingsboard/server/edge/BaseEdgeTest.java | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index 238e01f741..2d38fa9299 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -189,46 +189,60 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { @Test public void test() throws Exception { testReceivedInitialData(); - Assert.assertEquals(10, edgeImitator.getDownlinkMsgs().size()); + int expectedDownlinkSize = 10; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testDevices(); - Assert.assertEquals(14, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 4; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testAssets(); - Assert.assertEquals(18, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 4; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testRuleChains(); - Assert.assertEquals(21, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 3; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testDashboards(); - Assert.assertEquals(24, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 3; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testRelations(); - Assert.assertEquals(26, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 2; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testAlarms(); - Assert.assertEquals(29, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 3; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testEntityView(); - Assert.assertEquals(31, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 2; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testCustomer(); - Assert.assertEquals(33, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 2; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testWidgetsBundleAndWidgetType(); - Assert.assertEquals(37, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 4; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testTimeseries(); - Assert.assertEquals(38, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 1; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testAttributes(); - Assert.assertEquals(41, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 3; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testSendMessagesToCloud(); - Assert.assertEquals(50, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 9; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); testRpcCall(); - Assert.assertEquals(51, edgeImitator.getDownlinkMsgs().size()); + expectedDownlinkSize = expectedDownlinkSize + 1; + Assert.assertEquals(expectedDownlinkSize, edgeImitator.getDownlinkMsgs().size()); } private Device findDeviceByName(String deviceName) throws Exception { From f2eea6d82ba8a691df99f224a4849421dd124c60 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 15 Apr 2021 16:31:03 +0300 Subject: [PATCH 029/476] Do not print disconnect error msg --- .../thingsboard/server/controller/BaseEdgeControllerTest.java | 4 +++- .../test/java/org/thingsboard/server/edge/BaseEdgeTest.java | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java index cc50cdd00d..246749881f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java @@ -696,7 +696,9 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { Assert.assertTrue(edgeImitator.findMessageByType(UserUpdateMsg.class).isPresent()); edgeImitator.allowIgnoredTypes(); - edgeImitator.disconnect(); + try { + edgeImitator.disconnect(); + } catch (Exception ignored) {} doDelete("/api/device/" + savedDevice.getId().getId().toString()) .andExpect(status().isOk()); diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index 2d38fa9299..92548bb621 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -178,7 +178,9 @@ abstract public class BaseEdgeTest extends AbstractControllerTest { @After public void afterTest() throws Exception { - edgeImitator.disconnect(); + try { + edgeImitator.disconnect(); + } catch (Exception ignored) {} loginSysAdmin(); From b034fe04e6e392de00389a6307c7651ec77f4188 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Thu, 15 Apr 2021 16:33:57 +0300 Subject: [PATCH 030/476] Fix test wait condition --- .../server/controller/BaseEdgeEventControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java index f7ed71f177..c07a499f83 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java @@ -103,7 +103,7 @@ public class BaseEdgeEventControllerTest extends AbstractControllerTest { new TypeReference>() {}, new TimePageLink(4)).getData(); attempt++; Thread.sleep(100); - } while (edgeEvents.size() != 4 || attempt < 5); + } while (edgeEvents.size() != 4 && attempt < 5); Assert.assertEquals(4, edgeEvents.size()); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RULE_CHAIN.equals(ee.getType()))); Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.DEVICE.equals(ee.getType()))); From 2df1177ed92855c632000abb6d39dac33cc29ab1 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 16 Apr 2021 11:15:29 +0300 Subject: [PATCH 031/476] Replace StringUtils with our implementation --- .../server/common/data/StringUtils.java | 27 +++++++++++++++++++ .../server/common/data/event/DebugEvent.java | 2 +- .../common/data/event/ErrorEventFilter.java | 2 +- .../data/event/LifeCycleEventFilter.java | 2 +- .../data/event/StatisticsEventFilter.java | 2 +- 5 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java new file mode 100644 index 0000000000..cbe7094715 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.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.common.data; + +public class StringUtils { + + public static boolean isEmpty(String source) { + return source == null || source.isEmpty(); + } + + public static boolean isNotEmpty(String source) { + return source != null && !source.isEmpty(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java index 0e5bcbd015..a95949acbd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/DebugEvent.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.event; import lombok.Data; -import org.eclipse.leshan.core.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; @Data public abstract class DebugEvent implements EventFilter { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java index 59e6297a47..c9e9886c64 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/ErrorEventFilter.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.event; import lombok.Data; -import org.eclipse.leshan.core.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; @Data public class ErrorEventFilter implements EventFilter { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java index 4936a83e3c..a871ccb106 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/LifeCycleEventFilter.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.event; import lombok.Data; -import org.eclipse.leshan.core.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; @Data public class LifeCycleEventFilter implements EventFilter { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java index 0d28ea27e9..9fad8b4bca 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/event/StatisticsEventFilter.java @@ -16,7 +16,7 @@ package org.thingsboard.server.common.data.event; import lombok.Data; -import org.eclipse.leshan.core.util.StringUtils; +import org.thingsboard.server.common.data.StringUtils; @Data public class StatisticsEventFilter implements EventFilter { From 4a6e0c7aae3c305c5332c520d0447ebd697c805c Mon Sep 17 00:00:00 2001 From: nickAS21 <44275303+nickAS21@users.noreply.github.com> Date: Fri, 16 Apr 2021 12:12:11 +0300 Subject: [PATCH 032/476] Lwm2m fix bug 19 (#4422) * lwm2m: back: Write Attributes - start * lwm2m: front: fis bug disable instances * lwm2m: transport: attr/discavery start * lwm2m: transport: attributeLwm2m after update profile * lwm2m: transport: attributeLwm2m after update profile (no delete attribute) * Lwm2m: back: test * Lwm2m: back: test2 * Lwm2m: back: test3 * Lwm2m: transport fix bug Write attribute through the shared attribute does not work for Object 19 --- .../lwm2m/server/LwM2mTransportHandler.java | 8 ------ .../lwm2m/server/LwM2mTransportRequest.java | 18 ++++++++----- .../server/LwM2mTransportServiceImpl.java | 26 ++++++++++--------- 3 files changed, 26 insertions(+), 26 deletions(-) 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 afb8add332..ce65b072d6 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 @@ -362,14 +362,6 @@ public class LwM2mTransportHandler { } } - public static Integer validateObjectIdFromKey(String key) { - try { - return Integer.parseInt(key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[0]); - } catch (Exception e) { - return null; - } - } - public static String validateObjectVerFromKey(String key) { try { return (key.split(LWM2M_SEPARATOR_PATH)[1].split(LWM2M_SEPARATOR_KEY)[1]); 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 2274eaf2a9..2c728260d0 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 @@ -120,7 +120,7 @@ public class LwM2mTransportRequest { DownlinkRequest request = null; ContentFormat contentFormat = contentFormatParam != null ? ContentFormat.fromName(contentFormatParam.toUpperCase()) : null; LwM2mClient lwM2MClient = lwM2mClientContext.getLwM2mClientWithReg(registration, null); - ResourceModel resource = lwM2MClient.getResourceModel(targetIdVer); + ResourceModel resource = null; timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; switch (typeOper) { case GET_TYPE_OPER_READ: @@ -142,6 +142,7 @@ public class LwM2mTransportRequest { 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 { @@ -150,6 +151,7 @@ public class LwM2mTransportRequest { 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) { // if (contentFormat.equals(ContentFormat.TLV) && !resource.multiple) { if (contentFormat.equals(ContentFormat.TLV)) { @@ -171,7 +173,7 @@ public class LwM2mTransportRequest { } break; case PUT_TYPE_OPER_WRITE_ATTRIBUTES: - request = createWriteAttributeRequest (target, params); + request = createWriteAttributeRequest(target, params); break; } @@ -207,12 +209,16 @@ public class LwM2mTransportRequest { if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { this.handleResponse(registration, request.getPath().toString(), response, request); 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(), - ((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue().toString()); + 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(), - ((LwM2mSingleResource) ((WriteRequest) request).getNode()).getValue()); + log.debug("[{}] [{}] - [{}] [{}] Update SendRequest[{}]", registration.getEndpoint(), + ((Response) response.getCoapResponse()).getCode(), response.getCode(), + request.getPath().toString(), value); } } else { String msg = String.format("%s: sendRequest: CoapCode - %s Lwm2m code - %d name - %s Resource path - %s SendRequest to Client", LOG_LW2M_ERROR, 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 8ab70d3af4..d863504b44 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 @@ -95,7 +95,6 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandle 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.getAckCallback; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectIdFromKey; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportHandler.validateObjectVerFromKey; @Slf4j @@ -515,9 +514,10 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { null, null, this.lwM2mTransportContextServer.getLwM2MTransportConfigServer().getTimeout())); } // #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, GET_TYPE_OPER_DISCOVER, clientObjects); } } @@ -748,9 +748,7 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { if (resName != null && !resName.isEmpty()) { try { String resValue = this.getResourceValueToString(lwM2MClient, path); - if (resValue != null) { - parameters.addProperty(resName, resValue); - } + parameters.addProperty(resName, resValue); } catch (Exception e) { log.error("Failed to add parameters.", e); } @@ -907,10 +905,14 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { // send Request observe to Client registrationIds.forEach(registrationId -> { Registration registration = lwM2mClientContext.getRegistration(registrationId); - this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); + if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) { + this.readResourceValueObserve(registration, postObserveAnalyzer.getPathPostParametersAdd(), GET_TYPE_OPER_OBSERVE); + } // 5.3 del // send Request cancel observe to Client - this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel()); + if (postObserveAnalyzer.getPathPostParametersDel().size() > 0) { + this.cancelObserveIsValue(registration, postObserveAnalyzer.getPathPostParametersDel()); + } }); } } @@ -1253,11 +1255,11 @@ public class LwM2mTransportServiceImpl implements LwM2mTransportService { return new ArrayList<>(namesIsWritable); } - private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathKey, boolean isWritable) { - ResourceModel resourceModel = lwM2mClient.getResourceModel(pathKey); - Integer objectId = validateObjectIdFromKey(pathKey); - String objectVer = validateObjectVerFromKey(pathKey); - return resourceModel != null && (isWritable ? + private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { + ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer); + Integer objectId = new LwM2mPath(convertToObjectIdFromIdVer(pathIdVer)).getObjectId(); + String objectVer = validateObjectVerFromKey(pathIdVer); + return resourceModel != null && (isWritableNotOptional ? objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() : objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); } From 936171b80bd285f0b3d069e8b82a3ffb74c0cd7c Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 16 Apr 2021 11:57:09 +0300 Subject: [PATCH 033/476] UI: Improvement event statistics filter --- .../event/event-filter-panel.component.html | 9 +++++++++ .../components/event/event-filter-panel.component.ts | 12 ++++++++++-- .../home/components/event/event-table-config.ts | 4 ++-- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 +++ 4 files changed, 24 insertions(+), 4 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 0568a2d572..6a017f214e 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 @@ -29,6 +29,15 @@ + + + {{ column.title | translate}} + + + {{ 'event.min-value' | translate }} + + + {{ '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 3d016e361e..f60f7a09f7 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 @@ -15,7 +15,7 @@ /// import { Component, Inject, InjectionToken } from '@angular/core'; -import { FormBuilder, FormGroup } from '@angular/forms'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { OverlayRef } from '@angular/cdk/overlay'; import { EntityType } from '@shared/models/entity-type.models'; import { FilterEventBody } from '@shared/models/event.models'; @@ -59,7 +59,11 @@ export class EventFilterPanelComponent { this.eventFilterFormGroup = this.fb.group({}); this.data.columns.forEach((column) => { this.showColumns.push(column); - this.eventFilterFormGroup.addControl(column.key, this.fb.control(this.data.filterParams[column.key] || '')); + const validators = []; + if (this.isNumberFields(column.key)) { + validators.push(Validators.min(0)); + } + this.eventFilterFormGroup.addControl(column.key, this.fb.control(this.data.filterParams[column.key] || '', validators)); if (column.key === 'isError') { this.conditionError = true; } @@ -70,6 +74,10 @@ export class EventFilterPanelComponent { return ['msgDirectionType', 'status', 'entityName'].includes(key) ? key : ''; } + isNumberFields(key: string): string { + return ['messagesProcessed', 'errorsOccurred'].includes(key) ? key : ''; + } + selectorValues(key: string): string[] { switch (key) { case 'msgDirectionType': 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 3066de554b..1dc663684f 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 @@ -295,8 +295,8 @@ export class EventTableConfig extends EntityTableConfig { break; case EventType.STATS: this.filterColumns.push( - {key: 'messagesProcessed', title: 'event.messages-processed'}, - {key: 'errorsOccurred', title: 'event.errors-occurred'} + {key: 'messagesProcessed', title: 'event.min-messages-processed'}, + {key: 'errorsOccurred', title: 'event.min-errors-occurred'} ); break; case DebugEventType.DEBUG_RULE_NODE: 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 d028bf5158..a24bfd83ad 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1641,7 +1641,10 @@ "success": "Success", "failed": "Failed", "messages-processed": "Messages processed", + "min-messages-processed": "Minimum messages processed", "errors-occurred": "Errors occurred", + "min-errors-occurred": "Minimum errors occurred", + "min-value": "Minimum value is 0.", "all-events": "All", "has-error": "Has error", "entity-id": "Entity Id", From f1d89ace5d62409a2c433bfa47721df724e8ee84 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 16 Apr 2021 14:29:57 +0300 Subject: [PATCH 034/476] Removed invalid dependency --- .../server/controller/BaseController.java | 2 +- .../controller/TbResourceController.java | 8 +- .../service/install/InstallScripts.java | 4 +- .../resource/DefaultTbResourceService.java | 202 ++++++++++++++++++ .../service}/resource/TbResourceService.java | 12 +- .../transport/DefaultTransportApiService.java | 6 +- .../server/service/ServiceSqlTestSuite.java | 43 ++++ .../resource}/BaseTbResourceServiceTest.java | 56 ++++- .../server/dao/resource/ResourceService.java | 50 +++++ common/data/pom.xml | 4 - dao/pom.xml | 4 - ...eService.java => BaseResourceService.java} | 122 +---------- .../server/dao/tenant/TenantServiceImpl.java | 4 +- .../dao/service/AbstractServiceTest.java | 6 +- .../service/sql/TbResourceServiceSqlTest.java | 23 -- 15 files changed, 368 insertions(+), 178 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java rename {common/dao-api/src/main/java/org/thingsboard/server/dao => application/src/main/java/org/thingsboard/server/service}/resource/TbResourceService.java (90%) create mode 100644 application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java rename {dao/src/test/java/org/thingsboard/server/dao/service => application/src/test/java/org/thingsboard/server/service/resource}/BaseTbResourceServiceTest.java (88%) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java rename dao/src/main/java/org/thingsboard/server/dao/resource/{BaseTbResourceService.java => BaseResourceService.java} (55%) delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/sql/TbResourceServiceSqlTest.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 8d46cce8e1..bc9b91cec4 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -110,7 +110,6 @@ import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.resource.TbResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -130,6 +129,7 @@ import org.thingsboard.server.service.edge.rpc.init.SyncEdgeService; import org.thingsboard.server.service.lwm2m.LwM2MModelsRepository; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.service.resource.TbResourceService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; import org.thingsboard.server.service.security.permission.Operation; 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 3ac679e0d7..5e924a6d29 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TbResourceController.java @@ -36,8 +36,8 @@ import org.thingsboard.server.common.data.lwm2m.LwM2mObject; 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.dao.resource.TbResourceService; 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; @@ -52,12 +52,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 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 e2fb3ed657..67d981e2fa 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 @@ -35,7 +35,7 @@ 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.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -97,7 +97,7 @@ public class InstallScripts { private OAuth2ConfigTemplateService oAuth2TemplateService; @Autowired - private TbResourceService resourceService; + private ResourceService resourceService; private Path getTenantRuleChainsDir() { return Paths.get(getDataDir(), JSON_DIR, TENANT_DIR, RULE_CHAINS_DIR); 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 new file mode 100644 index 0000000000..81c59609ff --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/resource/DefaultTbResourceService.java @@ -0,0 +1,202 @@ +/** + * 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.resource; + +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +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.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.TbResourceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; +import org.thingsboard.server.common.data.lwm2m.LwM2mObject; +import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve; +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.resource.ResourceService; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Base64; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; +import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_SEARCH_TEXT; +import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID; +import static org.thingsboard.server.dao.service.Validator.validateId; + +@Slf4j +@Service +public class DefaultTbResourceService implements TbResourceService { + + private final ResourceService resourceService; + private final DDFFileParser ddfFileParser; + + public DefaultTbResourceService(ResourceService resourceService) { + this.resourceService = resourceService; + this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); + } + + @Override + public TbResource saveResource(TbResource resource) throws ThingsboardException { + log.trace("Executing saveResource [{}]", resource); + if (StringUtils.isEmpty(resource.getData())) { + throw new DataValidationException("Resource data should be specified!"); + } + if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { + try { + List objectModels = + ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); + if (!objectModels.isEmpty()) { + ObjectModel objectModel = objectModels.get(0); + + String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.getVersion(); + String name = objectModel.name; + resource.setResourceKey(resourceKey); + if (resource.getId() == null) { + resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.getVersion()); + } + resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); + } else { + throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); + } + } catch (InvalidDDFFileException | IOException e) { + throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); + } + if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource) == null) { + throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); + } + } else { + resource.setResourceKey(resource.getFileName()); + } + + return resourceService.saveResource(resource); + } + + @Override + public TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId) { + return resourceService.getResource(tenantId, resourceType, resourceId); + } + + @Override + public TbResource findResourceById(TenantId tenantId, TbResourceId resourceId) { + return resourceService.findResourceById(tenantId, resourceId); + } + + @Override + public TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId) { + return resourceService.findResourceInfoById(tenantId, resourceId); + } + + @Override + public PageData findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) { + return resourceService.findAllTenantResourcesByTenantId(tenantId, pageLink); + } + + @Override + public PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink) { + return resourceService.findTenantResourcesByTenantId(tenantId, pageLink); + } + + @Override + public List findLwM2mObject(TenantId tenantId, String sortOrder, String sortProperty, String[] objectIds) { + log.trace("Executing findByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + List resources = resourceService.findTenantResourcesByResourceTypeAndObjectIds(tenantId, ResourceType.LWM2M_MODEL, + objectIds); + return resources.stream() + .map(this::toLwM2mObject) + .sorted(getComparator(sortProperty, sortOrder)) + .collect(Collectors.toList()); + } + + @Override + public List findLwM2mObjectPage(TenantId tenantId, String sortProperty, String sortOrder, PageLink pageLink) { + log.trace("Executing findByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + PageData resourcePageData = resourceService.findTenantResourcesByResourceTypeAndPageLink(tenantId, ResourceType.LWM2M_MODEL, pageLink); + return resourcePageData.getData().stream() + .map(this::toLwM2mObject) + .sorted(getComparator(sortProperty, sortOrder)) + .collect(Collectors.toList()); + } + + @Override + public void deleteResource(TenantId tenantId, TbResourceId resourceId) { + resourceService.deleteResource(tenantId, resourceId); + } + + @Override + public void deleteResourcesByTenantId(TenantId tenantId) { + resourceService.deleteResourcesByTenantId(tenantId); + } + + private Comparator getComparator(String sortProperty, String sortOrder) { + Comparator comparator; + if ("name".equals(sortProperty)) { + comparator = Comparator.comparing(LwM2mObject::getName); + } else { + comparator = Comparator.comparingLong(LwM2mObject::getId); + } + return "DESC".equals(sortOrder) ? comparator.reversed() : comparator; + } + + private LwM2mObject toLwM2mObject(TbResource resource) { + try { + DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); + List objectModels = + ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); + if (objectModels.size() == 0) { + return null; + } else { + ObjectModel obj = objectModels.get(0); + LwM2mObject lwM2mObject = new LwM2mObject(); + lwM2mObject.setId(obj.id); + lwM2mObject.setKeyId(resource.getResourceKey()); + lwM2mObject.setName(obj.name); + lwM2mObject.setMultiple(obj.multiple); + lwM2mObject.setMandatory(obj.mandatory); + LwM2mInstance instance = new LwM2mInstance(); + instance.setId(0); + List resources = new ArrayList<>(); + obj.resources.forEach((k, v) -> { + if (!v.operations.isExecutable()) { + LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); + resources.add(lwM2MResourceObserve); + } + }); + instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); + lwM2mObject.setInstances(new LwM2mInstance[]{instance}); + return lwM2mObject; + } + } catch (IOException | InvalidDDFFileException e) { + log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e); + return null; + } + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java similarity index 90% rename from common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java rename to application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java index 0093b43dfe..cc35eac26e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/TbResourceService.java +++ b/application/src/main/java/org/thingsboard/server/service/resource/TbResourceService.java @@ -13,24 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.resource; +package org.thingsboard.server.service.resource; -import org.eclipse.leshan.core.model.InvalidDDFFileException; +import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; import org.thingsboard.server.common.data.TbResourceInfo; -import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.lwm2m.LwM2mObject; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import java.io.IOException; import java.util.List; - public interface TbResourceService { - TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException; + + TbResource saveResource(TbResource resource) throws ThingsboardException; TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); @@ -55,4 +54,5 @@ public interface TbResourceService { void deleteResource(TenantId tenantId, TbResourceId resourceId); void deleteResourcesByTenantId(TenantId 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 9613d708d2..6944809515 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 @@ -56,7 +56,7 @@ 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.relation.RelationService; -import org.thingsboard.server.dao.resource.TbResourceService; +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; @@ -108,7 +108,7 @@ public class DefaultTransportApiService implements TransportApiService { private final TbClusterService tbClusterService; private final DataDecodingEncodingService dataDecodingEncodingService; private final DeviceProvisionService deviceProvisionService; - private final TbResourceService resourceService; + private final ResourceService resourceService; private final ConcurrentMap deviceCreationLocks = new ConcurrentHashMap<>(); @@ -117,7 +117,7 @@ public class DefaultTransportApiService implements TransportApiService { RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, - DeviceProvisionService deviceProvisionService, TbResourceService resourceService) { + DeviceProvisionService deviceProvisionService, ResourceService resourceService) { this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; this.apiUsageStateService = apiUsageStateService; diff --git a/application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.java new file mode 100644 index 0000000000..0d470c78b9 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/ServiceSqlTestSuite.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.service; + +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.runner.RunWith; +import org.thingsboard.server.dao.CustomSqlUnit; +import org.thingsboard.server.queue.memory.InMemoryStorage; + +import java.util.Arrays; + +@RunWith(ClasspathSuite.class) +@ClasspathSuite.ClassnameFilters({ + "org.thingsboard.server.service.resource.*Test", + }) +public class ServiceSqlTestSuite { + + @ClassRule + public static CustomSqlUnit sqlUnit = new CustomSqlUnit( + Arrays.asList("sql/schema-types-hsql.sql", "sql/schema-ts-hsql.sql", "sql/schema-entities-hsql.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql"), + "sql/hsql/drop-all-tables.sql", + "sql-test.properties"); + + @BeforeClass + public static void cleanupInMemStorage(){ + InMemoryStorage.getInstance().cleanup(); + } +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java similarity index 88% rename from dao/src/test/java/org/thingsboard/server/dao/service/BaseTbResourceServiceTest.java rename to application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java index 21842af176..ffdefc27ed 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTbResourceServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/resource/BaseTbResourceServiceTest.java @@ -13,28 +13,37 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.dao.service; +package org.thingsboard.server.service.resource; 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.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.ResourceType; import org.thingsboard.server.common.data.TbResource; 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.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.Authority; +import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.AbstractServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.ArrayList; import java.util.Base64; import java.util.Collections; import java.util.List; -public abstract class BaseTbResourceServiceTest extends AbstractServiceTest { +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@DaoSqlTest +public class BaseTbResourceServiceTest extends AbstractControllerTest { private static final String LWM2M_TEST_MODEL = "\n" + "\n" + @@ -67,18 +76,38 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest { private TenantId tenantId; + @Autowired + private TbResourceService resourceService; + + private Tenant savedTenant; + private User tenantAdmin; + @Before - public void before() { + public void beforeTest() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); - Tenant savedTenant = tenantService.saveTenant(tenant); - Assert.assertNotNull(savedTenant); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); tenantId = savedTenant.getId(); + 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 after() { - tenantService.deleteTenant(tenantId); + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) + .andExpect(status().isOk()); } @Test @@ -239,9 +268,10 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest { @Test public void testFindTenantResourcesByTenantId() throws Exception { + loginSysAdmin(); Tenant tenant = new Tenant(); tenant.setTitle("Test tenant"); - tenant = tenantService.saveTenant(tenant); + tenant = doPost("/api/tenant", tenant, Tenant.class); TenantId tenantId = tenant.getId(); @@ -279,14 +309,17 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest { Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); - tenantService.deleteTenant(tenantId); + doDelete("/api/tenant/" + tenantId.getId().toString()) + .andExpect(status().isOk()); } @Test public void testFindAllTenantResourcesByTenantId() throws Exception { + loginSysAdmin(); + Tenant tenant = new Tenant(); tenant.setTitle("Test tenant"); - tenant = tenantService.saveTenant(tenant); + tenant = doPost("/api/tenant", tenant, Tenant.class); TenantId tenantId = tenant.getId(); @@ -344,7 +377,8 @@ public abstract class BaseTbResourceServiceTest extends AbstractServiceTest { Assert.assertFalse(pageData.hasNext()); Assert.assertTrue(pageData.getData().isEmpty()); - tenantService.deleteTenant(tenantId); + doDelete("/api/tenant/" + tenantId.getId().toString()) + .andExpect(status().isOk()); } } 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 new file mode 100644 index 0000000000..68c2c1f5f0 --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/resource/ResourceService.java @@ -0,0 +1,50 @@ +/** + * 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.resource; + +import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.TbResourceInfo; +import org.thingsboard.server.common.data.id.TbResourceId; +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 java.util.List; + +public interface ResourceService { + TbResource saveResource(TbResource resource); + + TbResource getResource(TenantId tenantId, ResourceType resourceType, String resourceId); + + TbResource findResourceById(TenantId tenantId, TbResourceId resourceId); + + TbResourceInfo findResourceInfoById(TenantId tenantId, TbResourceId resourceId); + + PageData findAllTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); + + PageData findTenantResourcesByTenantId(TenantId tenantId, PageLink pageLink); + + List findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType lwm2mModel, String[] objectIds); + + PageData findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType lwm2mModel, PageLink pageLink); + + void deleteResource(TenantId tenantId, TbResourceId resourceId); + + void deleteResourcesByTenantId(TenantId tenantId); + + +} diff --git a/common/data/pom.xml b/common/data/pom.xml index 92efc053a9..d011fcc5a7 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -87,10 +87,6 @@ org.thingsboard protobuf-dynamic - - org.eclipse.leshan - leshan-core - diff --git a/dao/pom.xml b/dao/pom.xml index 9ba3033970..cc25e1a13c 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -227,10 +227,6 @@ org.elasticsearch.client rest - - org.eclipse.leshan - leshan-core - diff --git a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseTbResourceService.java b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java similarity index 55% rename from dao/src/main/java/org/thingsboard/server/dao/resource/BaseTbResourceService.java rename to dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java index 0df2f19e80..97675d4b3e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/resource/BaseTbResourceService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/resource/BaseResourceService.java @@ -17,10 +17,6 @@ package org.thingsboard.server.dao.resource; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -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.hibernate.exception.ConstraintViolationException; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ResourceType; @@ -29,9 +25,6 @@ import org.thingsboard.server.common.data.TbResourceInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.lwm2m.LwM2mInstance; -import org.thingsboard.server.common.data.lwm2m.LwM2mObject; -import org.thingsboard.server.common.data.lwm2m.LwM2mResourceObserve; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.exception.DataValidationException; @@ -41,63 +34,30 @@ import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.tenant.TenantDao; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Base64; -import java.util.Comparator; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_KEY; -import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_SEARCH_TEXT; import static org.thingsboard.server.dao.device.DeviceServiceImpl.INCORRECT_TENANT_ID; import static org.thingsboard.server.dao.service.Validator.validateId; @Service @Slf4j -public class BaseTbResourceService implements TbResourceService { +public class BaseResourceService implements ResourceService { public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId "; private final TbResourceDao resourceDao; private final TbResourceInfoDao resourceInfoDao; private final TenantDao tenantDao; - private final DDFFileParser ddfFileParser; - public BaseTbResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao) { + + public BaseResourceService(TbResourceDao resourceDao, TbResourceInfoDao resourceInfoDao, TenantDao tenantDao) { this.resourceDao = resourceDao; this.resourceInfoDao = resourceInfoDao; this.tenantDao = tenantDao; - this.ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); } @Override - public TbResource saveResource(TbResource resource) throws InvalidDDFFileException, IOException { - log.trace("Executing saveResource [{}]", resource); - if (StringUtils.isEmpty(resource.getData())) { - throw new DataValidationException("Resource data should be specified!"); - } - if (ResourceType.LWM2M_MODEL.equals(resource.getResourceType())) { - List objectModels = - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); - if (!objectModels.isEmpty()) { - ObjectModel objectModel = objectModels.get(0); - - String resourceKey = objectModel.id + LWM2M_SEPARATOR_KEY + objectModel.getVersion(); - String name = objectModel.name; - resource.setResourceKey(resourceKey); - if (resource.getId() == null) { - resource.setTitle(name + " id=" + objectModel.id + " v" + objectModel.getVersion()); - } - resource.setSearchText(resourceKey + LWM2M_SEPARATOR_SEARCH_TEXT + name); - } else { - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); - } - } else { - resource.setResourceKey(resource.getFileName()); - } - + public TbResource saveResource(TbResource resource) { resourceValidator.validate(resource, TbResourceInfo::getTenantId); try { @@ -111,7 +71,6 @@ public class BaseTbResourceService implements TbResourceService { throw t; } } - } @Override @@ -156,31 +115,17 @@ public class BaseTbResourceService implements TbResourceService { } @Override - public List findLwM2mObjectPage(TenantId tenantId, String sortProperty, String sortOrder, PageLink pageLink) { - log.trace("Executing findByTenantId [{}]", tenantId); + public List findTenantResourcesByResourceTypeAndObjectIds(TenantId tenantId, ResourceType resourceType, String[] objectIds) { + log.trace("Executing findTenantResourcesByResourceTypeAndObjectIds [{}][{}][{}]", tenantId, resourceType, objectIds); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - PageData resourcePageData = resourceDao.findResourcesByTenantIdAndResourceType( - tenantId, - ResourceType.LWM2M_MODEL, pageLink); - return resourcePageData.getData().stream() - .map(this::toLwM2mObject) - .sorted(getComparator(sortProperty, sortOrder)) - .collect(Collectors.toList()); + return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, objectIds, null); } @Override - public List findLwM2mObject(TenantId tenantId, String sortOrder, - String sortProperty, - String[] objectIds) { - log.trace("Executing findByTenantId [{}]", tenantId); + public PageData findTenantResourcesByResourceTypeAndPageLink(TenantId tenantId, ResourceType resourceType, PageLink pageLink) { + log.trace("Executing findTenantResourcesByResourceTypeAndPageLink [{}][{}][{}]", tenantId, resourceType, pageLink); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - List resources = resourceDao.findResourcesByTenantIdAndResourceType(tenantId, ResourceType.LWM2M_MODEL, - objectIds, - null); - return resources.stream() - .map(this::toLwM2mObject) - .sorted(getComparator(sortProperty, sortOrder)) - .collect(Collectors.toList()); + return resourceDao.findResourcesByTenantIdAndResourceType(tenantId, resourceType, pageLink); } @Override @@ -190,50 +135,6 @@ public class BaseTbResourceService implements TbResourceService { tenantResourcesRemover.removeEntities(tenantId, tenantId); } - private LwM2mObject toLwM2mObject(TbResource resource) { - try { - DDFFileParser ddfFileParser = new DDFFileParser(new DefaultDDFFileValidator()); - List objectModels = - ddfFileParser.parseEx(new ByteArrayInputStream(Base64.getDecoder().decode(resource.getData())), resource.getSearchText()); - if (objectModels.size() == 0) { - return null; - } else { - ObjectModel obj = objectModels.get(0); - LwM2mObject lwM2mObject = new LwM2mObject(); - lwM2mObject.setId(obj.id); - lwM2mObject.setKeyId(resource.getResourceKey()); - lwM2mObject.setName(obj.name); - lwM2mObject.setMultiple(obj.multiple); - lwM2mObject.setMandatory(obj.mandatory); - LwM2mInstance instance = new LwM2mInstance(); - instance.setId(0); - List resources = new ArrayList<>(); - obj.resources.forEach((k, v) -> { - if (!v.operations.isExecutable()) { - LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); - resources.add(lwM2MResourceObserve); - } - }); - instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); - lwM2mObject.setInstances(new LwM2mInstance[]{instance}); - return lwM2mObject; - } - } catch (IOException | InvalidDDFFileException e) { - log.error("Could not parse the XML of objectModel with name [{}]", resource.getSearchText(), e); - return null; - } - } - - private Comparator getComparator(String sortProperty, String sortOrder) { - Comparator comparator; - if ("name".equals(sortProperty)) { - comparator = Comparator.comparing(LwM2mObject::getName); - } else { - comparator = Comparator.comparingLong(LwM2mObject::getId); - } - return "DESC".equals(sortOrder) ? comparator.reversed() : comparator; - } - private DataValidator resourceValidator = new DataValidator<>() { @Override @@ -259,9 +160,6 @@ public class BaseTbResourceService implements TbResourceService { throw new DataValidationException("Resource is referencing to non-existent tenant!"); } } - if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource) == null) { - throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); - } } }; 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 9a08c65fef..b514b43cf3 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,7 +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.resource.TbResourceService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -90,7 +90,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe private RuleChainService ruleChainService; @Autowired - private TbResourceService resourceService; + private ResourceService resourceService; @Override public Tenant findTenantById(TenantId 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 cfd232b330..0761f28d33 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 @@ -54,7 +54,7 @@ import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.relation.RelationService; -import org.thingsboard.server.dao.resource.TbResourceService; +import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -152,9 +152,9 @@ public abstract class AbstractServiceTest { protected DeviceProfileService deviceProfileService; @Autowired - protected TbResourceService resourceService; + protected ResourceService resourceService; - class IdComparator implements Comparator { + public class IdComparator implements Comparator { @Override public int compare(D o1, D o2) { return o1.getId().getId().compareTo(o2.getId().getId()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/TbResourceServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/TbResourceServiceSqlTest.java deleted file mode 100644 index 2af4881c84..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/sql/TbResourceServiceSqlTest.java +++ /dev/null @@ -1,23 +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.dao.service.sql; - -import org.thingsboard.server.dao.service.BaseTbResourceServiceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; - -@DaoSqlTest -public class TbResourceServiceSqlTest extends BaseTbResourceServiceTest { -} From e50892516851144c1454be1fc36336ed9144a0d7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 16 Apr 2021 14:34:39 +0300 Subject: [PATCH 035/476] Fix for statistics collection period --- application/src/main/resources/thingsboard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index c5e6c69ff5..5ba575a413 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -322,7 +322,7 @@ actors: # Enable/disable actor statistics enabled: "${ACTORS_STATISTICS_ENABLED:true}" js_print_interval_ms: "${ACTORS_JS_STATISTICS_PRINT_INTERVAL_MS:10000}" - persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:10000}" + persist_frequency: "${ACTORS_STATISTICS_PERSIST_FREQUENCY:3600000}" cache: # caffeine or redis From 9b2009a50131bfd4cb4b36551d4158ac41ace56f Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 16 Apr 2021 14:59:35 +0300 Subject: [PATCH 036/476] UI: Refactoring dashboard logo --- .../dashboard-page.component.html | 34 +++++++----------- .../dashboard-page.component.scss | 12 +++---- .../dashboard-page.component.ts | 23 ++++-------- .../dashboard-settings-dialog.component.html | 17 ++++----- .../dashboard-settings-dialog.component.ts | 9 ++--- .../dashboard-toolbar.component.scss | 12 ------- .../dashboard-toolbar.component.ts | 2 -- .../src/app/shared/models/dashboard.models.ts | 4 +-- ui-ngx/src/assets/example_logo.png | Bin 34158 -> 0 bytes .../assets/locale/locale.constant-en_US.json | 5 ++- ui-ngx/src/assets/nasa.png | Bin 99886 -> 0 bytes 11 files changed, 39 insertions(+), 79 deletions(-) delete mode 100644 ui-ngx/src/assets/example_logo.png delete mode 100644 ui-ngx/src/assets/nasa.png 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 1160acf808..7344a14310 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 @@ -46,26 +46,18 @@ view_compact - -
- - - - - - -
+ + +
@@ -297,4 +289,4 @@ -
\ No newline at end of file + diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss index ed795e129e..6ace69d312 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.scss @@ -54,12 +54,9 @@ div.tb-dashboard-page { z-index: 13; pointer-events: none; - .db-logo-and-title { - height: $toolbar-height; - .whitelabel-logo-db{ - height: 100%; - width: auto; - } + .dashboard_logo{ + height: 75%; + margin-right: 16px; } &.tb-dashboard-toolbar-opened { @@ -71,9 +68,8 @@ div.tb-dashboard-page { right: 18px; transition: right .3s cubic-bezier(.55, 0, .55, .2) .2s; } - + } - } .tb-dashboard-container { &.tb-dashboard-toolbar-opened { &.is-fullscreen { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 1b5f16dae6..78bc07c3d0 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -121,8 +121,6 @@ import { DisplayWidgetTypesPanelData, WidgetTypes } from '@home/components/dashboard-page/widget-types-panel.component'; -import { LogoComponent } from '@app/shared/components/logo.component'; -import { concat } from 'lodash'; import { DashboardWidgetSelectComponent } from '@home/components/dashboard-page/dashboard-widget-select.component'; // @dynamic @@ -192,9 +190,8 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC addingLayoutCtx: DashboardPageLayoutContext; - logo_name_local = 'example_logo.png'; - dashboard_logo = 'assets/' + this.logo_name_local; - + logo = 'assets/logo_title_white.svg'; + dashboardCtx: DashboardContext = { instanceId: this.utils.guid(), getDashboard: () => this.dashboard, @@ -490,15 +487,14 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC isDefined(this.dashboard.configuration.settings.showDashboardLogo)) { return this.dashboard.configuration.settings.showDashboardLogo; } else { - return true; + return false; } } - public updateLogoName(): string { - this.dashboard_logo = 'assets/' + this.dashboard.configuration.settings.logoName - return this.dashboard.configuration.settings.logoName; + public get dashboardLogo(): string { + return this.dashboard.configuration.settings.dashboardLogoUrl || this.logo; } - + public showRightLayoutSwitch(): boolean { return this.isMobile && this.layouts.right.show; } @@ -621,14 +617,9 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC settings: deepClone(this.dashboard.configuration.settings), gridSettings, } - } - ).afterClosed().subscribe((data) => { + }).afterClosed().subscribe((data) => { if (data) { this.dashboard.configuration.settings = data.settings; - if (this.dashboard.configuration.settings) - { - this.updateLogoName(); - } const newGridSettings = data.gridSettings; if (newGridSettings) { const layout = this.dashboard.configuration.states[layoutKeys.state].layouts[layoutKeys.layout]; 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 f745a8508d..3a20d146dd 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 @@ -51,15 +51,9 @@ openOnInput formControlName="titleColor"> - - dashboard.display-dashboard-enterLogoName - - -
- - {{ 'dashboard.display-dashboard-showDashboardLogo' | translate }} - +
{{ 'dashboard.display-dashboards-selection' | translate }} @@ -76,6 +70,13 @@ {{ 'dashboard.display-dashboard-export' | translate }}
+ + {{ 'dashboard.display-dashboard-toolbar-logo' | translate }} + + +
, protected router: Router, @@ -73,7 +69,6 @@ export class DashboardSettingsDialogComponent extends DialogComponent div { - height: $toolbar-height; - .whitelabel-logo-db { - width: auto; - max-width: 100%; - height: 30px; - max-height: 100%; - margin-left: 20px; - margin-top: 5px; - margin-right: 30px; - } - } @media #{$mat-gt-sm} { height: $toolbar-height; diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.ts index 176375acb3..201f70787a 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-toolbar.component.ts @@ -43,6 +43,4 @@ export class DashboardToolbarComponent implements OnInit { this.triggerClick.emit(); } - white_label_logo = 'assets/white_label_logo.png'; - } diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 5e96c31ea0..6a90101923 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -86,8 +86,8 @@ export interface DashboardSettings { showDashboardsSelect?: boolean; showEntitiesSelect?: boolean; showFilters?: boolean; - showDashboardLogo?: boolean; - logoName?: string; + showDashboardLogo?: boolean; + dashboardLogoUrl?: string; showDashboardTimewindow?: boolean; showDashboardExport?: boolean; toolbarAlwaysOpen?: boolean; diff --git a/ui-ngx/src/assets/example_logo.png b/ui-ngx/src/assets/example_logo.png deleted file mode 100644 index 44cd0245616ead898328fe8c2549c8268d3d4a7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 34158 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 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 cd3741bca8..1f7f733dac 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -725,9 +725,8 @@ "display-filters": "Display filters", "display-dashboard-timewindow": "Display timewindow", "display-dashboard-export": "Display export", - "display-dashboard-showDashboardLogo": "Display dashboard logo", - "display-dashboard-enterLogoName": "Enter logo filename (must be in assets folder)", - "display-dashboard-exampleLogoName": "example_logo.png", + "display-dashboard-toolbar-logo": "Display dashboard logo in fullscreen mode", + "dashboard-logo-image": "Dashboard image logo", "import": "Import dashboard", "export": "Export dashboard", "export-failed-error": "Unable to export dashboard: {{error}}", diff --git a/ui-ngx/src/assets/nasa.png b/ui-ngx/src/assets/nasa.png deleted file mode 100644 index 027407a775edb3c513c78c587ea24c61d57c9192..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99886 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 z 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 JiR{Cm{{iIW1R?+c From a4347776adeb084323e4ba7e33f685dc6ba94679 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 16 Apr 2021 15:40:44 +0300 Subject: [PATCH 037/476] 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 038/476] 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 039/476] 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 040/476] 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 041/476] 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 042/476] 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 043/476] 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 044/476] 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 045/476] 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 046/476] 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 047/476] 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 048/476] 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 049/476] 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 050/476] 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 051/476] 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 052/476] 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 053/476] 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 054/476] 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 055/476] 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 056/476] 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 057/476] 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 058/476] 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 059/476] 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 060/476] 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 061/476] [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 062/476] 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 063/476] 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 064/476] 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 065/476] 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 066/476] 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 067/476] 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 068/476] 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 069/476] 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 070/476] 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 071/476] 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 072/476] 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 073/476] 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 074/476] 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 075/476] 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 076/476] 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 077/476] 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 078/476] 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 079/476] 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 080/476] 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 081/476] 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 082/476] 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 083/476] 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 084/476] 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 085/476] 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 086/476] 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 087/476] 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 088/476] 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 089/476] 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 090/476] 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 091/476] 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 092/476] 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 093/476] 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 094/476] 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 095/476] 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 096/476] 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 097/476] 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 098/476] 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 099/476] 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 98cb4c8e80b94c56238efb7b584bbfddbdc20b18 Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Mon, 26 Apr 2021 17:58:31 +0300 Subject: [PATCH 100/476] Fixed HTML action of identical widgets --- .../src/main/data/json/system/widget_bundles/cards.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index fc6d4ce590..302478381e 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -36,7 +36,7 @@ "resources": [], "templateHtml": "", "templateCss": "", - "controllerScript": "self.onInit = function() {\n\n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml));\n cardHtml = '
' + \n self.ctx.settings.cardHtml + \n '
';\n self.ctx.$container.html(cardHtml);\n\n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", + "controllerScript": "self.onInit = function() {\n\n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml + self.ctx.widget.id));\n cardHtml = '
' + \n self.ctx.settings.cardHtml + \n '
';\n self.ctx.$container.html(cardHtml);\n\n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.onDestroy = function() {\n}\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"
HTML code here
\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"cardHtml\":\"
HTML code here
\",\"cardCss\":\".card {\\n font-weight: bold;\\n font-size: 32px;\\n color: #999;\\n width: 100%;\\n height: 100%;\\n display: flex;\\n align-items: center;\\n justify-content: center;\\n}\"},\"title\":\"HTML Card\",\"dropShadow\":true}" @@ -72,7 +72,7 @@ "resources": [], "templateHtml": "", "templateCss": "", - "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlValueCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml));\n self.ctx.html = '
' + \n self.ctx.settings.cardHtml + \n '
';\n\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label == 'entityLabel') {\n variableInfo.isEntityLabel = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && !variableInfo.isEntityLabel && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n singleEntity: true,\n dataKeysOptional: true\n };\n}\n\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var $injector = self.ctx.$scope.$injector;\n var utils = $injector.get(self.ctx.servicesMap.get('utils'));\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n } else if (variableInfo.isEntityLabel) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityLabel || self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n text = replaceCustomTranslations(text);\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n \n function replaceCustomTranslations (pattern) {\n var customTranslationRegex = new RegExp('{i18n:[^{}]+}', 'g');\n pattern = pattern.replace(customTranslationRegex, getTranslationText);\n return pattern;\n }\n \n function getTranslationText (variable) {\n return utils.customTranslation(variable, variable);\n \n }\n}\n\n", + "controllerScript": "self.onInit = function() {\n self.ctx.varsRegex = /\\$\\{([^\\}]*)\\}/g;\n self.ctx.htmlSet = false;\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = 'html-value-card-' + hashCode(self.ctx.settings.cardCss);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, self.ctx.settings.cardCss);\n self.ctx.$container.addClass(namespace);\n var evtFnPrefix = 'htmlValueCard_' + Math.abs(hashCode(self.ctx.settings.cardCss + self.ctx.settings.cardHtml + self.ctx.widget.id));\n self.ctx.html = '
' + \n self.ctx.settings.cardHtml + \n '
';\n\n self.ctx.replaceInfo = processHtmlPattern(self.ctx.html, self.ctx.data);\n \n updateHtml();\n \n window[evtFnPrefix + '_onClickFn'] = function (event) {\n self.ctx.actionsApi.elementClick(event);\n }\n\n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n function processHtmlPattern(pattern, data) {\n var match = self.ctx.varsRegex.exec(pattern);\n var replaceInfo = {};\n replaceInfo.variables = [];\n while (match !== null) {\n var variableInfo = {};\n variableInfo.dataKeyIndex = -1;\n var variable = match[0];\n var label = match[1];\n var valDec = 2;\n var splitVals = label.split(':');\n if (splitVals.length > 1) {\n label = splitVals[0];\n valDec = parseFloat(splitVals[1]);\n }\n variableInfo.variable = variable;\n variableInfo.valDec = valDec;\n if (label == 'entityName') {\n variableInfo.isEntityName = true;\n } else if (label == 'entityLabel') {\n variableInfo.isEntityLabel = true;\n } else if (label.startsWith('#')) {\n var keyIndexStr = label.substring(1);\n var n = Math.floor(Number(keyIndexStr));\n if (String(n) === keyIndexStr && n >= 0) {\n variableInfo.dataKeyIndex = n;\n }\n }\n if (!variableInfo.isEntityName && !variableInfo.isEntityLabel && variableInfo.dataKeyIndex === -1) {\n for (var i = 0; i < data.length; i++) {\n var datasourceData = data[i];\n var dataKey = datasourceData.dataKey;\n if (dataKey.label === label) {\n variableInfo.dataKeyIndex = i;\n break;\n }\n }\n }\n replaceInfo.variables.push(variableInfo);\n match = self.ctx.varsRegex.exec(pattern);\n }\n return replaceInfo;\n } \n}\n\nself.onDataUpdated = function() {\n updateHtml();\n}\n\nself.actionSources = function() {\n return {\n 'elementClick': {\n name: 'widget-action.element-click',\n multiple: true\n }\n };\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n singleEntity: true,\n dataKeysOptional: true\n };\n}\n\n\nself.onDestroy = function() {\n}\n\nfunction isNumber(n) {\n return !isNaN(parseFloat(n)) && isFinite(n);\n}\n\nfunction padValue(val, dec, int) {\n var i = 0;\n var s, strVal, n;\n\n val = parseFloat(val);\n n = (val < 0);\n val = Math.abs(val);\n\n if (dec > 0) {\n strVal = val.toFixed(dec).toString().split('.');\n s = int - strVal[0].length;\n\n for (; i < s; ++i) {\n strVal[0] = '0' + strVal[0];\n }\n\n strVal = (n ? '-' : '') + strVal[0] + '.' + strVal[1];\n }\n\n else {\n strVal = Math.round(val).toString();\n s = int - strVal.length;\n\n for (; i < s; ++i) {\n strVal = '0' + strVal;\n }\n\n strVal = (n ? '-' : '') + strVal;\n }\n\n return strVal;\n}\n\nfunction updateHtml() {\n var $injector = self.ctx.$scope.$injector;\n var utils = $injector.get(self.ctx.servicesMap.get('utils'));\n var text = self.ctx.html;\n var updated = false;\n for (var v in self.ctx.replaceInfo.variables) {\n var variableInfo = self.ctx.replaceInfo.variables[v];\n var txtVal = '';\n if (variableInfo.dataKeyIndex > -1) {\n var varData = self.ctx.data[variableInfo.dataKeyIndex].data;\n if (varData.length > 0) {\n var val = varData[varData.length-1][1];\n if (isNumber(val)) {\n txtVal = padValue(val, variableInfo.valDec, 0);\n } else {\n txtVal = val;\n }\n }\n } else if (variableInfo.isEntityName) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n } else if (variableInfo.isEntityLabel) {\n if (self.ctx.defaultSubscription.datasources.length) {\n txtVal = self.ctx.defaultSubscription.datasources[0].entityLabel || self.ctx.defaultSubscription.datasources[0].entityName;\n } else {\n txtVal = 'Unknown';\n }\n }\n if (typeof variableInfo.lastVal === undefined ||\n variableInfo.lastVal !== txtVal) {\n updated = true;\n variableInfo.lastVal = txtVal;\n }\n text = text.split(variableInfo.variable).join(txtVal);\n }\n if (updated || !self.ctx.htmlSet) {\n text = replaceCustomTranslations(text);\n self.ctx.$container.html(text);\n if (!self.ctx.htmlSet) {\n self.ctx.htmlSet = true;\n }\n }\n \n function replaceCustomTranslations (pattern) {\n var customTranslationRegex = new RegExp('{i18n:[^{}]+}', 'g');\n pattern = pattern.replace(customTranslationRegex, getTranslationText);\n return pattern;\n }\n \n function getTranslationText (variable) {\n return utils.customTranslation(variable, variable);\n \n }\n}\n\n", "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Settings\",\n \"required\": [\"cardHtml\"],\n \"properties\": {\n \"cardCss\": {\n \"title\": \"CSS\",\n \"type\": \"string\",\n \"default\": \".card {\\n font-weight: bold; \\n}\"\n },\n \"cardHtml\": {\n \"title\": \"HTML\",\n \"type\": \"string\",\n \"default\": \"
HTML code here
\"\n }\n }\n },\n \"form\": [\n {\n \"key\": \"cardCss\",\n \"type\": \"css\"\n }, \n {\n \"key\": \"cardHtml\",\n \"type\": \"html\"\n } \n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"My value\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"return Math.random() * 5.45;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":false,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"0px\",\"settings\":{\"cardCss\":\".card {\\n width: 100%;\\n height: 100%;\\n border: 2px solid #ccc;\\n box-sizing: border-box;\\n}\\n\\n.card .content {\\n padding: 20px;\\n display: flex;\\n flex-direction: row;\\n align-items: center;\\n justify-content: space-around;\\n height: 100%;\\n box-sizing: border-box;\\n}\\n\\n.card .content .column {\\n display: flex;\\n flex-direction: column; \\n justify-content: space-around;\\n height: 100%;\\n}\\n\\n.card h1 {\\n text-transform: uppercase;\\n color: #999;\\n font-size: 20px;\\n font-weight: bold;\\n margin: 0;\\n padding-bottom: 10px;\\n line-height: 32px;\\n}\\n\\n.card .value {\\n font-size: 38px;\\n font-weight: 200;\\n}\\n\\n.card .description {\\n font-size: 20px;\\n color: #999;\\n}\\n\",\"cardHtml\":\"
\\n
\\n
\\n

Value title

\\n
\\n ${My value:2} units.\\n
\\n
\\n Value description text\\n
\\n
\\n \\n
\\n
\"},\"title\":\"HTML Value Card\",\"dropShadow\":false,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" From 1850024fdb7902583d509f1788f8f3a834d10572 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Apr 2021 10:26:16 +0300 Subject: [PATCH 101/476] 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 2c7ab1ac34c7ddda59c63c8988c8b481bd47e8d9 Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Wed, 28 Apr 2021 10:42:29 +0300 Subject: [PATCH 102/476] Added 'Use entity lable in tab name' checkbox in Timeseries table --- .../main/data/json/system/widget_bundles/cards.json | 2 +- .../widget/lib/timeseries-table-widget.component.html | 2 +- .../widget/lib/timeseries-table-widget.component.ts | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index fc6d4ce590..2917c44c39 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -55,7 +55,7 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"defaultPageSize\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useDeviceLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useDeviceLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"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\"}" } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index f1c37d4cdd..aec30d12ec 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -39,7 +39,7 @@ - +
Date: Wed, 28 Apr 2021 10:45:18 +0300 Subject: [PATCH 103/476] Refactoring --- .../components/widget/lib/timeseries-table-widget.component.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index 352b689508..e82aa1c27e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -93,7 +93,6 @@ interface TimeseriesTableSource { header: TimeseriesHeader[]; stylesInfo: CellStyleInfo[]; contentsInfo: CellContentInfo[]; - useDeviceLabel: boolean, rowDataTemplate: {[key: string]: any}; } From aee3fd3b6e477b8b77bc85c58a10e58bffb1ce16 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Apr 2021 11:29:04 +0300 Subject: [PATCH 104/476] 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 23ece9c247ca5a0a8b83d6565ddd4902c2e79917 Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Wed, 28 Apr 2021 11:30:10 +0300 Subject: [PATCH 105/476] Refactoring --- .../widget/lib/timeseries-table-widget.component.html | 2 +- .../widget/lib/timeseries-table-widget.component.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index aec30d12ec..a92b4e16bb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -39,7 +39,7 @@ - +
Date: Wed, 28 Apr 2021 11:33:58 +0300 Subject: [PATCH 106/476] Refactoring --- .../components/widget/lib/timeseries-table-widget.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index 3654c1f708..ffbfbb94c0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -234,7 +234,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.updateDatasources(); } - public getTabLabel(source){ + public getTabLabel(source: TimeseriesTableSource){ if(this.useDeviceLabel){ return source.datasource.entityLabel || source.datasource.entityName; } else { From 0127611204ca2ff3c3c0434d8f0b23174ca7ef3e Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Wed, 28 Apr 2021 12:05:32 +0300 Subject: [PATCH 107/476] Refactoring --- .../src/main/data/json/system/widget_bundles/cards.json | 2 +- .../widget/lib/timeseries-table-widget.component.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 2917c44c39..52d8920f2d 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -55,7 +55,7 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.timeseriesTableWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n ignoreDataUpdateOnIntervalTick: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'actionCellButton': {\n name: 'widget-action.action-cell-button',\n multiple: true\n },\n 'rowClick': {\n name: 'widget-action.row-click',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useDeviceLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useDeviceLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"enableSearch\": {\n \"title\": \"Enable search\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyHeader\": {\n \"title\": \"Always display header\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"enableStickyAction\": {\n \"title\": \"Always display actions column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"showMilliseconds\": {\n \"title\": \"Display timestamp milliseconds\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"useEntityLabel\": {\n \"title\": \"Use entity label in tab name\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n },\n \"hideEmptyLines\": {\n \"title\": \"Hide empty lines\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"disableStickyHeader\": {\n \"title\": \"Disable sticky header\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"useRowStyleFunction\": {\n \"title\": \"Use row style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"rowStyleFunction\": {\n \"title\": \"Row style function: f(rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"enableSearch\",\n \"enableStickyHeader\",\n \"enableStickyAction\",\n \"showTimestamp\",\n \"showMilliseconds\",\n \"displayPagination\",\n \"useEntityLabel\",\n \"defaultPageSize\",\n \"identifyDeviceSelector\",\n \"hideEmptyLines\",\n \"useRowStyleFunction\",\n {\n \"key\": \"rowStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useRowStyleFunction === true\"\n }\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellStyleFunction === true\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\",\n \"condition\": \"model.useCellContentFunction === true\"\n }\n ]\n}", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"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\"}" } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index ffbfbb94c0..a866a37780 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -129,7 +129,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI private defaultSortOrder = '-0'; private hideEmptyLines = false; public showTimestamp = true; - private useDeviceLabel = false; + private useEntityLabel = false; private dateFormatFilter: string; private rowStylesInfo: RowStyleInfo; @@ -206,7 +206,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI this.enableStickyHeader = isDefined(this.settings.enableStickyHeader) ? this.settings.enableStickyHeader : true; this.enableStickyAction = isDefined(this.settings.enableStickyAction) ? this.settings.enableStickyAction : true; this.hideEmptyLines = isDefined(this.settings.hideEmptyLines) ? this.settings.hideEmptyLines : false; - this.useDeviceLabel = isDefined(this.widgetConfig.settings.useDeviceLabel) ? this.widgetConfig.settings.useDeviceLabel : false; + this.useEntityLabel = isDefined(this.widgetConfig.settings.useEntityLabel) ? this.widgetConfig.settings.useEntityLabel : false; this.showTimestamp = this.settings.showTimestamp !== false; this.dateFormatFilter = (this.settings.showMilliseconds !== true) ? 'yyyy-MM-dd HH:mm:ss' : 'yyyy-MM-dd HH:mm:ss.SSS'; @@ -235,7 +235,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI } public getTabLabel(source: TimeseriesTableSource){ - if(this.useDeviceLabel){ + if(this.useEntityLabel){ return source.datasource.entityLabel || source.datasource.entityName; } else { return source.datasource.entityName; From 8a5a4f0995048ed1d7787bb91bccab135220a7c3 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Wed, 28 Apr 2021 12:26:35 +0300 Subject: [PATCH 108/476] Added ability to autogenerate checksum for firmware --- .../server/controller/FirmwareController.java | 13 +++-- .../server/dao/firmware/FirmwareService.java | 4 ++ .../transport/mqtt/MqttTransportHandler.java | 3 -- .../dao/firmware/BaseFirmwareService.java | 48 +++++++++++-------- ui-ngx/src/app/core/http/firmware.service.ts | 10 ++-- .../pages/firmware/firmwares.component.html | 7 +-- .../pages/firmware/firmwares.component.ts | 23 +-------- .../src/app/shared/models/firmware.models.ts | 4 +- 8 files changed, 46 insertions(+), 66 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 c8f10b3d78..9fcf5a7ec9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java +++ b/application/src/main/java/org/thingsboard/server/controller/FirmwareController.java @@ -115,7 +115,7 @@ public class FirmwareController extends BaseController { @ResponseBody public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, @RequestParam(required = false) String checksum, - @RequestParam(required = false) String checksumAlgorithm, + @RequestParam() String checksumAlgorithm, @RequestBody MultipartFile file) throws ThingsboardException { checkParameter(FIRMWARE_ID, strFirmwareId); try { @@ -129,18 +129,17 @@ 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(data).toString(); + ByteBuffer data = ByteBuffer.wrap(file.getBytes()); + if (StringUtils.isEmpty(checksum)) { + checksum = firmwareService.generateChecksum(checksumAlgorithm, data); } firmware.setChecksumAlgorithm(checksumAlgorithm); firmware.setChecksum(checksum); firmware.setFileName(file.getOriginalFilename()); firmware.setContentType(file.getContentType()); - firmware.setData(ByteBuffer.wrap(data)); - firmware.setDataSize((long) data.length); + firmware.setData(data); + firmware.setDataSize((long) data.array().length); return firmwareService.saveFirmware(firmware); } catch (Exception e) { throw handleException(e); 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..0ba34ae90f 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,12 +22,16 @@ 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 java.nio.ByteBuffer; + public interface FirmwareService { FirmwareInfo saveFirmwareInfo(FirmwareInfo firmwareInfo); Firmware saveFirmware(Firmware firmware); + String generateChecksum(String checksumAlgorithm, ByteBuffer data); + Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); FirmwareInfo findFirmwareInfoById(TenantId tenantId, FirmwareId firmwareId); 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..2b990d6b6b 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 @@ -456,9 +456,6 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement 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); } 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..c2b5394ae2 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 @@ -101,6 +101,32 @@ public class BaseFirmwareService implements FirmwareService { } } + @Override + public String generateChecksum(String checksumAlgorithm, ByteBuffer data) { + + if (data == null || !data.hasArray() || data.array().length == 0) { + throw new DataValidationException("Firmware data should be specified!"); + } + + HashFunction hashFunction; + switch (checksumAlgorithm) { + 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!"); + } + + return hashFunction.hashBytes(data.array()).toString(); + } + + @Override public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) { log.trace("Executing findFirmwareById [{}]", firmwareId); @@ -217,11 +243,6 @@ public class BaseFirmwareService implements FirmwareService { 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!"); - } - if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) { throw new DataValidationException("Firmware checksum algorithm should be specified!"); } @@ -229,22 +250,7 @@ public class BaseFirmwareService implements FirmwareService { 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!"); - } - - String currentChecksum = hashFunction.hashBytes(data.array()).toString(); + String currentChecksum = generateChecksum(firmware.getChecksumAlgorithm(), firmware.getData()); 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 index 7bf42fb49e..60d16d6bdf 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 { ChecksumAlgorithm, Firmware, FirmwareInfo } from '@shared/models/firmware.models'; import { catchError, map, mergeMap } from 'rxjs/operators'; import { deepClone, isDefinedAndNotNull } from '@core/utils'; @@ -100,16 +100,16 @@ export class FirmwareService { return this.http.post('/api/firmware', firmware, defaultHttpOptionsFromConfig(config)); } - public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm?: string, + public uploadFirmwareFile(firmwareId: string, file: File, checksumAlgorithm: ChecksumAlgorithm, 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}`; + let url = `/api/firmware/${firmwareId}?checksumAlgorithm=${checksumAlgorithm}`; + if (checksum) { + url += `&checksum=${checksum}`; } return this.http.post(url, formData, defaultHttpUploadOptions(config.ignoreLoading, config.ignoreErrors, config.resendRequest)); 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..21a5ad327e 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 @@ -72,7 +72,6 @@ firmware.checksum-algorithm - {{ checksumAlgorithmTranslationMap.get(checksumAlgorithm) }} @@ -80,11 +79,7 @@ firmware.checksum - - - {{ 'firmware.checksum-required' | 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 8ffef239b4..5e3c26dfee 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 @@ -23,7 +23,6 @@ import { EntityTableConfig } from '@home/models/entity/entities-table-config.mod 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({ @@ -45,26 +44,6 @@ export class FirmwaresComponent extends EntityComponent implements OnI 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(); @@ -83,7 +62,7 @@ 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], + checksumAlgorithm: [entity && entity.checksumAlgorithm ? entity.checksumAlgorithm : ChecksumAlgorithm.SHA256], checksum: [entity ? entity.checksum : '', Validators.maxLength(1020)], additionalInfo: this.fb.group( { diff --git a/ui-ngx/src/app/shared/models/firmware.models.ts b/ui-ngx/src/app/shared/models/firmware.models.ts index 65dd1761bf..147901a0cc 100644 --- a/ui-ngx/src/app/shared/models/firmware.models.ts +++ b/ui-ngx/src/app/shared/models/firmware.models.ts @@ -38,8 +38,8 @@ export interface FirmwareInfo extends BaseData { version?: string; hasData?: boolean; fileName: string; - checksum?: ChecksumAlgorithm; - checksumAlgorithm?: string; + checksum?: string; + checksumAlgorithm: ChecksumAlgorithm; contentType: string; dataSize?: number; additionalInfo?: any; From 83e31f42631f21cb3ac5ef99059d6922dce84b5e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Apr 2021 12:44:57 +0300 Subject: [PATCH 109/476] 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 110/476] 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 111/476] 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 112/476] 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 113/476] 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 114/476] 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 115/476] 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 116/476] 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 117/476] 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 118/476] 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 119/476] 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 7a4d302c858a98c5958c0ef516cbaadcc24c6c87 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 29 Apr 2021 09:35:02 +0300 Subject: [PATCH 120/476] Changes according to comments from @ashvayka --- .../server/controller/FirmwareController.java | 10 ++-- .../firmware/DefaultFirmwareStateService.java | 2 +- .../server/dao/firmware/FirmwareService.java | 6 ++- .../server/common/data/FirmwareInfo.java | 3 +- .../data/firmware/ChecksumAlgorithm.java | 26 +++++++++ .../dao/firmware/BaseFirmwareService.java | 54 +++++++++++++++---- .../server/dao/model/sql/FirmwareEntity.java | 6 ++- .../dao/model/sql/FirmwareInfoEntity.java | 8 ++- .../service/BaseDeviceProfileServiceTest.java | 3 +- .../dao/service/BaseDeviceServiceTest.java | 3 +- .../dao/service/BaseFirmwareServiceTest.java | 3 +- .../src/app/shared/models/firmware.models.ts | 16 ++++-- 12 files changed, 115 insertions(+), 25 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java 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 9fcf5a7ec9..d740d452f3 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,6 @@ */ 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; @@ -33,6 +32,7 @@ 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.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -49,6 +49,7 @@ import java.nio.ByteBuffer; public class FirmwareController extends BaseController { public static final String FIRMWARE_ID = "firmwareId"; + public static final String CHECKSUM_ALGORITHM = "checksumAlgorithm"; @PreAuthorize("hasAnyAuthority( 'TENANT_ADMIN')") @RequestMapping(value = "/firmware/{firmwareId}/download", method = RequestMethod.GET) @@ -115,9 +116,10 @@ public class FirmwareController extends BaseController { @ResponseBody public Firmware saveFirmwareData(@PathVariable(FIRMWARE_ID) String strFirmwareId, @RequestParam(required = false) String checksum, - @RequestParam() String checksumAlgorithm, + @RequestParam(CHECKSUM_ALGORITHM) String checksumAlgorithmStr, @RequestBody MultipartFile file) throws ThingsboardException { checkParameter(FIRMWARE_ID, strFirmwareId); + checkParameter(CHECKSUM_ALGORITHM, checksumAlgorithmStr); try { FirmwareId firmwareId = new FirmwareId(toUUID(strFirmwareId)); FirmwareInfo info = checkFirmwareInfoId(firmwareId, Operation.READ); @@ -129,6 +131,8 @@ public class FirmwareController extends BaseController { firmware.setVersion(info.getVersion()); firmware.setAdditionalInfo(info.getAdditionalInfo()); + ChecksumAlgorithm checksumAlgorithm = ChecksumAlgorithm.valueOf(checksumAlgorithmStr.toUpperCase()); + ByteBuffer data = ByteBuffer.wrap(file.getBytes()); if (StringUtils.isEmpty(checksum)) { checksum = firmwareService.generateChecksum(checksumAlgorithm, data); @@ -139,7 +143,7 @@ public class FirmwareController extends BaseController { firmware.setFileName(file.getOriginalFilename()); firmware.setContentType(file.getContentType()); firmware.setData(data); - firmware.setDataSize((long) data.array().length); + firmware.setDataSize((long) data.capacity()); 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 6e0e3fd246..58b44a52ce 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 @@ -227,7 +227,7 @@ public class DefaultFirmwareStateService implements FirmwareStateService { attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_VERSION, firmware.getVersion()))); 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_ALGORITHM, firmware.getChecksumAlgorithm().name()))); attributes.add(new BaseAttributeKvEntry(ts, new StringDataEntry(DataConstants.FIRMWARE_CHECKSUM, firmware.getChecksum()))); telemetryService.saveAndNotify(tenantId, deviceId, DataConstants.SHARED_SCOPE, attributes, new FutureCallback<>() { @Override 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 0ba34ae90f..3edc950300 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 @@ -17,6 +17,8 @@ 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.exception.ThingsboardException; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -30,7 +32,9 @@ public interface FirmwareService { Firmware saveFirmware(Firmware firmware); - String generateChecksum(String checksumAlgorithm, ByteBuffer data); + PageData getSupportedChecksumAlgorithms(PageLink pageLink); + + String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) throws ThingsboardException; Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); 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..b0617f16bd 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 @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; @@ -34,7 +35,7 @@ public class FirmwareInfo extends SearchTextBasedWithAdditionalInfo private boolean hasData; private String fileName; private String contentType; - private String checksumAlgorithm; + private ChecksumAlgorithm checksumAlgorithm; private String checksum; private Long dataSize; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java b/common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.java new file mode 100644 index 0000000000..3998482e35 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/firmware/ChecksumAlgorithm.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.firmware; + +public enum ChecksumAlgorithm { + MD5, + SHA256, + SHA384, + SHA512, + CRC32, + MURMUR3_32, + MURMUR3_128 +} 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 c2b5394ae2..e3dc4f15cc 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 @@ -26,6 +26,9 @@ 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.exception.ThingsboardErrorCode; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.FirmwareId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -35,8 +38,13 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TenantDao; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.Optional; import static org.thingsboard.server.common.data.CacheConstants.FIRMWARE_CACHE; @@ -102,7 +110,7 @@ public class BaseFirmwareService implements FirmwareService { } @Override - public String generateChecksum(String checksumAlgorithm, ByteBuffer data) { + public String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) throws ThingsboardException { if (data == null || !data.hasArray() || data.array().length == 0) { throw new DataValidationException("Firmware data should be specified!"); @@ -110,22 +118,43 @@ public class BaseFirmwareService implements FirmwareService { HashFunction hashFunction; switch (checksumAlgorithm) { - case "sha256": + case MD5: + hashFunction = Hashing.md5(); + break; + case SHA256: hashFunction = Hashing.sha256(); break; - case "md5": - hashFunction = Hashing.md5(); + case SHA384: + hashFunction = Hashing.sha384(); break; - case "crc32": + case SHA512: + hashFunction = Hashing.sha512(); + break; + case CRC32: hashFunction = Hashing.crc32(); break; + case MURMUR3_32: + hashFunction = Hashing.murmur3_32(); + break; + case MURMUR3_128: + hashFunction = Hashing.murmur3_128(); + break; default: throw new DataValidationException("Unknown checksum algorithm!"); } - return hashFunction.hashBytes(data.array()).toString(); } + @Override + public PageData getSupportedChecksumAlgorithms(PageLink pageLink) { + List methodNames = new ArrayList<>(); + Method[] hashingMethods = Hashing.class.getMethods(); + Arrays.stream(hashingMethods).forEach(method -> { + methodNames.add(method.getName()); + }); + return new PageData<>(methodNames, 1, methodNames.size(), false); + } + @Override public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) { @@ -150,7 +179,8 @@ public class BaseFirmwareService implements FirmwareService { } @Override - public PageData findTenantFirmwaresByTenantIdAndHasData(TenantId tenantId, boolean hasData, PageLink pageLink) { + 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); @@ -243,14 +273,20 @@ public class BaseFirmwareService implements FirmwareService { throw new DataValidationException("Firmware content type should be specified!"); } - if (StringUtils.isEmpty(firmware.getChecksumAlgorithm())) { + if (firmware.getChecksumAlgorithm() == null) { throw new DataValidationException("Firmware checksum algorithm should be specified!"); } if (StringUtils.isEmpty(firmware.getChecksum())) { throw new DataValidationException("Firmware checksum should be specified!"); } - String currentChecksum = generateChecksum(firmware.getChecksumAlgorithm(), firmware.getData()); + String currentChecksum; + + try { + currentChecksum = generateChecksum(firmware.getChecksumAlgorithm(), firmware.getData()); + } catch (ThingsboardException e) { + throw new DataValidationException(e.getMessage()); + } if (!currentChecksum.equals(firmware.getChecksum())) { throw new DataValidationException("Wrong firmware file!"); 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..8713a33842 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.firmware.ChecksumAlgorithm; 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; @@ -68,8 +71,9 @@ public class FirmwareEntity extends BaseSqlEntity implements SearchTex @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) private String contentType; + @Enumerated(EnumType.STRING) @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN) - private String checksumAlgorithm; + private ChecksumAlgorithm checksumAlgorithm; @Column(name = FIRMWARE_CHECKSUM_COLUMN) private String checksum; 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..91cfd1b5e1 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.firmware.ChecksumAlgorithm; 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; @@ -70,8 +73,9 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S @Column(name = FIRMWARE_CONTENT_TYPE_COLUMN) private String contentType; + @Enumerated(EnumType.STRING) @Column(name = FIRMWARE_CHECKSUM_ALGORITHM_COLUMN) - private String checksumAlgorithm; + private ChecksumAlgorithm checksumAlgorithm; @Column(name = FIRMWARE_CHECKSUM_COLUMN) private String checksum; @@ -108,7 +112,7 @@ public class FirmwareInfoEntity extends BaseSqlEntity implements S } public FirmwareInfoEntity(UUID id, long createdTime, UUID tenantId, String title, String version, - String fileName, String contentType, String checksumAlgorithm, String checksum, Long dataSize, + String fileName, String contentType, ChecksumAlgorithm checksumAlgorithm, String checksum, Long dataSize, Object additionalInfo, boolean hasData) { this.id = id; this.createdTime = createdTime; 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..fd1097d7e1 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.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -101,7 +102,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { firmware.setVersion("v1.0"); firmware.setFileName("test.txt"); firmware.setContentType("text/plain"); - firmware.setChecksumAlgorithm("sha256"); + firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); firmware.setData(ByteBuffer.wrap(new byte[]{1})); Firmware savedFirmware = firmwareService.saveFirmware(firmware); 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..c08b2748d2 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 @@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.Firmware; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.firmware.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -187,7 +188,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { firmware.setVersion("v1.0"); firmware.setFileName("test.txt"); firmware.setContentType("text/plain"); - firmware.setChecksumAlgorithm("sha256"); + firmware.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); firmware.setChecksum("4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"); firmware.setData(ByteBuffer.wrap(new byte[]{1})); Firmware savedFirmware = firmwareService.saveFirmware(firmware); 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..da459efc9b 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 @@ -26,6 +26,7 @@ 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.ChecksumAlgorithm; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -42,7 +43,7 @@ public abstract class BaseFirmwareServiceTest extends AbstractServiceTest { 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 ChecksumAlgorithm CHECKSUM_ALGORITHM = ChecksumAlgorithm.SHA256; private static final String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; private static final ByteBuffer DATA = ByteBuffer.wrap(new byte[]{1}); diff --git a/ui-ngx/src/app/shared/models/firmware.models.ts b/ui-ngx/src/app/shared/models/firmware.models.ts index 147901a0cc..39775395d7 100644 --- a/ui-ngx/src/app/shared/models/firmware.models.ts +++ b/ui-ngx/src/app/shared/models/firmware.models.ts @@ -19,16 +19,24 @@ import { TenantId } from '@shared/models/id/tenant-id'; import { FirmwareId } from '@shared/models/id/firmware-id'; export enum ChecksumAlgorithm { - MD5 = 'md5', - SHA256 = 'sha256', - CRC32 = 'crc32' + MD5 = 'MD5', + SHA256 = 'SHA256', + SHA384 = 'SHA384', + SHA512 = 'SHA512', + CRC32 = 'CRC32', + MURMUR3_32 = 'MURMUR3_32', + MURMUR3_128 = 'MURMUR3_128' } export const ChecksumAlgorithmTranslationMap = new Map( [ [ChecksumAlgorithm.MD5, 'MD5'], [ChecksumAlgorithm.SHA256, 'SHA-256'], - [ChecksumAlgorithm.CRC32, 'CRC-32'] + [ChecksumAlgorithm.SHA384, 'SHA-384'], + [ChecksumAlgorithm.SHA512, 'SHA-512'], + [ChecksumAlgorithm.CRC32, 'CRC-32'], + [ChecksumAlgorithm.MURMUR3_32, 'MURMUR3-32'], + [ChecksumAlgorithm.MURMUR3_128, 'MURMUR3-128'] ] ); From 1de97ad0e1586d3917755cc257ff0bbf8d5e43e7 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Thu, 29 Apr 2021 11:12:53 +0300 Subject: [PATCH 121/476] 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 122/476] 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 123/476] 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 124/476] 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 125/476] 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 126/476] 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 127/476] 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 128/476] 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 129/476] 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 130/476] 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 131/476] 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 132/476] 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 133/476] 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 134/476] 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 b45d514b8332a893a076edb455d76c00d65dcc99 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 30 Apr 2021 08:53:00 +0300 Subject: [PATCH 135/476] Removed unused method for getting supported checksum algorithms --- .../server/dao/firmware/FirmwareService.java | 2 -- .../server/dao/firmware/BaseFirmwareService.java | 11 ----------- 2 files changed, 13 deletions(-) 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 2362197a66..6dbd534228 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 @@ -33,8 +33,6 @@ public interface FirmwareService { Firmware saveFirmware(Firmware firmware); - PageData getSupportedChecksumAlgorithms(PageLink pageLink); - String generateChecksum(ChecksumAlgorithm checksumAlgorithm, ByteBuffer data) throws ThingsboardException; Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId); 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 4e1913acfe..b17c01ad47 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 @@ -148,17 +148,6 @@ public class BaseFirmwareService implements FirmwareService { return hashFunction.hashBytes(data.array()).toString(); } - @Override - public PageData getSupportedChecksumAlgorithms(PageLink pageLink) { - List methodNames = new ArrayList<>(); - Method[] hashingMethods = Hashing.class.getMethods(); - Arrays.stream(hashingMethods).forEach(method -> { - methodNames.add(method.getName()); - }); - return new PageData<>(methodNames, 1, methodNames.size(), false); - } - - @Override public Firmware findFirmwareById(TenantId tenantId, FirmwareId firmwareId) { log.trace("Executing findFirmwareById [{}]", firmwareId); From f264aa43302dd70f9dfd39cd8475939889a117de Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 29 Apr 2021 10:02:51 +0300 Subject: [PATCH 136/476] 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 137/476] 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 138/476] 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 139/476] 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 140/476] 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 141/476] 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 fc837f3f1bc3a9fc73c6a4775e6664e30c70bfd1 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 30 Apr 2021 12:50:40 +0300 Subject: [PATCH 142/476] UI: Rename LWM2M tab name --- .../lwm2m-device-profile-transport-configuration.component.html | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) 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 5363d05d74..a29605a527 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 @@ -51,7 +51,7 @@
- +
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 57727fa40f..5a9d90ebb2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1227,7 +1227,6 @@ "required": " value is required.", "mode": "Security config mode", "pattern_hex_dec": "{ count, plural, 0 {must be hex decimal format} other {must be # characters} }", - "bootstrap-tab": "Bootstrap", "servers": "Servers", "short-id": "Short ID", "short-id-tip": "Short Server ID", From ff334b54389f9a4ceda9ea6e7fa3cb4415b16128 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 30 Apr 2021 14:22:36 +0300 Subject: [PATCH 143/476] 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 144/476] 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 145/476] 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 146/476] 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 147/476] 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 148/476] 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 149/476] 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 150/476] 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 151/476] 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 152/476] 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 153/476] 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 154/476] 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; + } } From 7322afac0be4c15be9e47aae8e054367e5a17410 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 5 May 2021 19:09:15 +0300 Subject: [PATCH 155/476] Implementation draft --- .../src/main/resources/thingsboard.yml | 1 + .../TbCoapDtlsCertificateVerifier.java | 1 - ...va => LwM2MTransportBootstrapService.java} | 3 +- .../secure/LwM2MBootstrapSecurityStore.java | 22 +-- .../config/LwM2MTransportServerConfig.java | 4 +- .../lwm2m/secure/EndpointSecurityInfo.java | 5 +- ...LwM2mCredentialsSecurityInfoValidator.java | 10 +- .../lwm2m/secure/TbLwM2MAuthorizer.java | 28 +++ .../TbLwM2MDtlsCertificateVerifier.java | 177 ++++++++++++++++++ .../secure/TbLwM2MDtlsSessionStorage.java | 26 +++ .../server/DefaultLwM2mTransportService.java | 81 +++----- .../server/LwM2mTransportServerHelper.java | 19 +- .../lwm2m/server/client/LwM2mClient.java | 29 +-- .../common/transport/TransportService.java | 2 +- .../service/DefaultTransportService.java | 26 ++- 15 files changed, 326 insertions(+), 108 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/{LwM2MTransportBootstrapServerConfiguration.java => LwM2MTransportBootstrapService.java} (99%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index d14adc0573..ae7fbda6d9 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -643,6 +643,7 @@ transport: private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" # Only Certificate_x509: alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" + skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: enable: "${LWM2M_ENABLED_BS:true}" id: "${LWM2M_SERVER_ID_BS:111}" diff --git a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java index 1de7bb1693..2076c7a354 100644 --- a/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java +++ b/common/coap-server/src/main/java/org/thingsboard/server/coapserver/TbCoapDtlsCertificateVerifier.java @@ -145,7 +145,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri @Override public void setResultHandler(HandshakeResultHandler resultHandler) { - // empty implementation } public ConcurrentMap getTbCoapDtlsSessionIdsMap() { 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/LwM2MTransportBootstrapService.java similarity index 99% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapServerConfiguration.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 16bb97aac5..9348cb31a5 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/LwM2MTransportBootstrapService.java @@ -65,7 +65,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g @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')") @RequiredArgsConstructor -public class LwM2MTransportBootstrapServerConfiguration { +//TODO: @ybondarenko please refactor this to be similar to DefaultLwM2mTransportService +public class LwM2MTransportBootstrapService { private PublicKey publicKey; private PrivateKey privateKey; private boolean pskMode = false; 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 0a55d2c3cb..22e0540c09 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 @@ -103,7 +103,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { BootstrapConfig bsConfig = store.getBootstrapConfig(); if (bsConfig.security != null) { try { - bootstrapConfigStore.add(store.getEndPoint(), bsConfig); + bootstrapConfigStore.add(store.getEndpoint(), bsConfig); } catch (InvalidConfigurationException e) { log.error("", e); } @@ -121,22 +121,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) { /* Use RPK only */ case PSK: - store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndPoint(), + store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(), lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(), Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray()))); store.setSecurityMode(SecurityMode.PSK.code); break; case RPK: try { - store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndPoint(), + store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(), SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray())))); store.setSecurityMode(SecurityMode.RPK.code); break; } catch (IOException | GeneralSecurityException e) { - log.error("Unable to decode Client public key for [{}] [{}]", store.getEndPoint(), e.getMessage()); + log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage()); } case X509: - store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndPoint())); + store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint())); store.setSecurityMode(SecurityMode.X509.code); break; case NO_SEC: @@ -166,22 +166,22 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { 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()); + String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LW2M_INFO, store.getEndpoint()); 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()); + 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()); helper.sendParametersOnThingsboardTelemetry(helper.getKvLogyToThingsboard(logMsg), sessionInfo); return null; } } } catch (JsonProcessingException e) { - log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndPoint(), e.getMessage()); + log.error("Unable to decode Json or Certificate for [{}] [{}]", store.getEndpoint(), e.getMessage()); return null; } - log.error("Unable to decode Json or Certificate for [{}]", store.getEndPoint()); + log.error("Unable to decode Json or Certificate for [{}]", store.getEndpoint()); return null; } 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 cd8287da4c..109898812b 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 @@ -148,9 +148,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { keyStoreValue = KeyStore.getInstance(keyStoreType); keyStoreValue.load(inKeyStore, keyStorePassword == null ? null : keyStorePassword.toCharArray()); } catch (Exception 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); + log.info("Unable to lookup LwM2M keystore. Reason: {}, {}" , uri, e.getMessage()); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java index 851934a806..f65acf04bd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java @@ -20,19 +20,20 @@ import lombok.Data; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE; @Data public class EndpointSecurityInfo { - private ValidateDeviceCredentialsResponseMsg msg; + private ValidateDeviceCredentialsResponse msg; private SecurityInfo securityInfo; private int securityMode = DEFAULT_MODE.code; /** bootstrap */ private DeviceProfile deviceProfile; private JsonObject bootstrapJsonCredential; - private String endPoint; + private String endpoint; private BootstrapConfig bootstrapConfig; } 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 0263e72fb2..ec5200602c 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 @@ -24,6 +24,7 @@ import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; @@ -59,12 +60,11 @@ public class LwM2mCredentialsSecurityInfoValidator { context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(endpoint).build(), new TransportServiceCallback<>() { @Override - public void onSuccess(ValidateDeviceCredentialsResponseMsg msg) { - String credentialsBody = msg.getCredentialsBody(); + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + String credentialsBody = msg.getCredentials(); resultSecurityStore[0] = createSecurityInfo(endpoint, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); - Optional deviceProfileOpt = LwM2mTransportUtil.decode(msg.getProfileBody().toByteArray()); - deviceProfileOpt.ifPresent(profile -> resultSecurityStore[0].setDeviceProfile(profile)); + resultSecurityStore[0].setDeviceProfile(msg.getDeviceProfile()); latch.countDown(); } @@ -105,7 +105,7 @@ public class LwM2mCredentialsSecurityInfoValidator { 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()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java new file mode 100644 index 0000000000..3719ce7246 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.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.transport.lwm2m.secure; + +import org.eclipse.leshan.core.request.Identity; +import org.eclipse.leshan.core.request.UplinkRequest; +import org.eclipse.leshan.server.registration.Registration; +import org.eclipse.leshan.server.security.Authorizer; + +public class TbLwM2MAuthorizer implements Authorizer { + @Override + public Registration isAuthorized(UplinkRequest request, Registration registration, Identity senderIdentity) { + return null; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java new file mode 100644 index 0000000000..c3d9d85d04 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -0,0 +1,177 @@ +/** + * 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.secure; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.util.CertPathUtil; +import org.eclipse.californium.scandium.dtls.AlertMessage; +import org.eclipse.californium.scandium.dtls.CertificateMessage; +import org.eclipse.californium.scandium.dtls.CertificateType; +import org.eclipse.californium.scandium.dtls.CertificateVerificationResult; +import org.eclipse.californium.scandium.dtls.ConnectionId; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; +import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; +import org.eclipse.californium.scandium.dtls.x509.StaticCertificateVerifier; +import org.eclipse.californium.scandium.util.ServerNames; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import org.thingsboard.server.common.transport.util.SslUtil; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; + +import javax.annotation.PostConstruct; +import javax.security.auth.x500.X500Principal; +import java.security.PublicKey; +import java.security.cert.CertPath; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +@Slf4j +@Component +@RequiredArgsConstructor +public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { + + private final TransportService transportService; + private final TbLwM2MDtlsSessionStorage sessionStorage; + private final LwM2MTransportServerConfig config; + + @SuppressWarnings("deprecation") + private StaticCertificateVerifier staticCertificateVerifier; + + @Value("${transport.lwm2m.server.security.skip_validity_check_for_client_cert:false}") + private boolean skipValidityCheckForClientCert; + + @Override + public List getSupportedCertificateType() { + return Arrays.asList(CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY); + } + + @PostConstruct + public void init() { + try { + /* by default trust all */ + X509Certificate[] trustedCertificates = new X509Certificate[0]; + if (config.getKeyStoreValue() != null) { + X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias()); + if (rootCAX509Cert != null) { + trustedCertificates = new X509Certificate[1]; + trustedCertificates[0] = rootCAX509Cert; + } + } + staticCertificateVerifier = new StaticCertificateVerifier(trustedCertificates); + } catch (Exception e) { + log.info("Failed to initialize the "); + } + } + + @Override + public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) { + CertPath certChain = message.getCertificateChain(); + if (certChain == null) { + //We trust all RPK on this layer, and use TbLwM2MAuthorizer + PublicKey publicKey = message.getPublicKey(); + return new CertificateVerificationResult(cid, publicKey, null); + } else { + try { + String credentialsBody = null; + CertPath certpath = message.getCertificateChain(); + X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); + for (X509Certificate cert : chain) { + try { + if (!skipValidityCheckForClientCert) { + cert.checkValidity(); + } + + String strCert = SslUtil.getCertificateString(cert); + String sha3Hash = EncryptionUtil.getSha3Hash(strCert); + final ValidateDeviceCredentialsResponse[] deviceCredentialsResponse = new ValidateDeviceCredentialsResponse[1]; + CountDownLatch latch = new CountDownLatch(1); + transportService.process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(sha3Hash).build(), + new TransportServiceCallback<>() { + @Override + public void onSuccess(ValidateDeviceCredentialsResponse msg) { + if (!StringUtils.isEmpty(msg.getCredentials())) { + deviceCredentialsResponse[0] = msg; + } + latch.countDown(); + } + + @Override + public void onError(Throwable e) { + log.error(e.getMessage(), e); + latch.countDown(); + } + }); + latch.await(10, TimeUnit.SECONDS); + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; + if (msg != null && strCert.equals(msg.getCredentials())) { + credentialsBody = msg.getCredentials(); + DeviceProfile deviceProfile = msg.getDeviceProfile(); + if (msg.hasDeviceInfo() && deviceProfile != null) { + String endpoint = sha3Hash; //TODO: extract endpoint from credentials body and push to storage + sessionStorage.put(endpoint, msg); + } + break; + } + } catch (InterruptedException | + CertificateEncodingException | + CertificateExpiredException | + CertificateNotYetValidException e) { + log.error(e.getMessage(), e); + } + } + if (credentialsBody == null) { + if (staticCertificateVerifier != null) { + staticCertificateVerifier.verifyCertificate(message, session); + } else { + AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR, + session.getPeer()); + throw new HandshakeException("x509 verification not enabled!", alert); + } + } + return new CertificateVerificationResult(cid, certpath, null); + } catch (HandshakeException e) { + log.trace("Certificate validation failed!", e); + return new CertificateVerificationResult(cid, e, null); + } + } + } + + @Override + public List getAcceptedIssuers() { + return CertPathUtil.toSubjects(null); + } + + @Override + public void setResultHandler(HandshakeResultHandler resultHandler) { + + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java new file mode 100644 index 0000000000..26c38a6327 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.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.transport.lwm2m.secure; + +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; + +public interface TbLwM2MDtlsSessionStorage { + + void put(String endpoint, ValidateDeviceCredentialsResponse msg); + + ValidateDeviceCredentialsResponse get(String endpoint); + +} 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 3df1263a3f..8bd0fdaff2 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 @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; import org.eclipse.leshan.core.util.Hex; @@ -25,14 +26,14 @@ import org.eclipse.leshan.server.californium.LeshanServer; import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; 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.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.secure.TbLwM2MAuthorizer; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MDtlsCertificateVerifier; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -41,7 +42,6 @@ import javax.annotation.PreDestroy; import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.KeyFactory; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; @@ -70,9 +70,10 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mNetworkConfig.g @RequiredArgsConstructor public class DefaultLwM2mTransportService implements LwM2MTransportService { + public static final CipherSuite[] RPK_OR_X509_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256, TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256}; + public static final CipherSuite[] PSK_CIPHER_SUITES = {TLS_PSK_WITH_AES_128_CCM_8, TLS_PSK_WITH_AES_128_CBC_SHA256}; private PublicKey publicKey; private PrivateKey privateKey; - private boolean pskMode = false; private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; @@ -81,6 +82,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private final CaliforniumRegistrationStore registrationStore; private final EditableSecurityStore securityStore; private final LwM2mClientContext lwM2mClientContext; + private final TbLwM2MDtlsCertificateVerifier certificateVerifier; + private final TbLwM2MAuthorizer authorizer; private LeshanServer server; @@ -128,9 +131,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { config.setModelProvider(modelProvider); builder.setObjectModelProvider(modelProvider); - /* Create credentials */ - this.setServerWithCredentials(builder); - /* Set securityStore with new registrationStore */ builder.setSecurityStore(securityStore); builder.setRegistrationStore(registrationStore); @@ -142,18 +142,8 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { dtlsConfig.setServerOnly(true); dtlsConfig.setRecommendedSupportedGroupsOnly(config.isRecommendedSupportedGroups()); dtlsConfig.setRecommendedCipherSuitesOnly(config.isRecommendedCiphers()); - if (this.pskMode) { - dtlsConfig.setSupportedCipherSuites( - TLS_PSK_WITH_AES_128_CCM_8, - TLS_PSK_WITH_AES_128_CBC_SHA256); - } else { - dtlsConfig.setSupportedCipherSuites( - TLS_PSK_WITH_AES_128_CCM_8, - TLS_PSK_WITH_AES_128_CBC_SHA256, - TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8, - TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256); - } - + /* Create credentials */ + this.setServerWithCredentials(builder, dtlsConfig); /* Set DTLS Config */ builder.setDtlsConfig(dtlsConfig); @@ -162,40 +152,21 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { return builder.build(); } - private void setServerWithCredentials(LeshanServerBuilder builder) { - try { - if (config.getKeyStoreValue() != null) { - if (this.setBuilderX509(builder)) { - X509Certificate rootCAX509Cert = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getRootCertificateAlias()); - if (rootCAX509Cert != null) { - X509Certificate[] trustedCertificates = new X509Certificate[1]; - trustedCertificates[0] = rootCAX509Cert; - builder.setTrustedCertificates(trustedCertificates); - } else { - /* by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); - } - /* Set securityStore with registrationStore*/ - builder.setAuthorizer(new DefaultAuthorizer(securityStore, new SecurityChecker() { - @Override - protected boolean matchX509Identity(String endpoint, String receivedX509CommonName, - String expectedX509CommonName) { - return endpoint.startsWith(expectedX509CommonName); - } - })); - } - } else if (this.setServerRPK(builder)) { - this.infoPramsUri("RPK"); - this.infoParamsServerKey(this.publicKey, this.privateKey); - } else { - /* by default trust all */ - builder.setTrustedCertificates(new X509Certificate[0]); - log.info("Unable to load X509 files for LWM2MServer"); - this.pskMode = true; - this.infoPramsUri("PSK"); - } - } catch (KeyStoreException ex) { - log.error("[{}] Unable to load X509 files server", ex.getMessage()); + private void setServerWithCredentials(LeshanServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) { + if (config.getKeyStoreValue() != null && this.setBuilderX509(builder)) { + dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier); + builder.setAuthorizer(authorizer); + dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES); + } else if (this.setServerRPK(builder)) { + this.infoPramsUri("RPK"); + this.infoParamsServerKey(this.publicKey, this.privateKey); + dtlsConfig.setSupportedCipherSuites(RPK_OR_X509_CIPHER_SUITES); + } else { + /* by default trust all */ + builder.setTrustedCertificates(new X509Certificate[0]); + log.info("Unable to load X509 files for LWM2MServer"); + dtlsConfig.setSupportedCipherSuites(PSK_CIPHER_SUITES); + this.infoPramsUri("PSK"); } } @@ -241,7 +212,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private boolean setServerRPK(LeshanServerBuilder builder) { try { - this.generateKeyForRPK(); + this.loadOrGenerateRPKKeys(); if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && this.privateKey != null && this.privateKey.getEncoded().length > 0) { builder.setPublicKey(this.publicKey); @@ -254,7 +225,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { return false; } - private void generateKeyForRPK() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { + private void loadOrGenerateRPKKeys() throws NoSuchAlgorithmException, InvalidParameterSpecException, InvalidKeySpecException { /* Get Elliptic Curve Parameter spec for secp256r1 */ AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); algoParameters.init(new ECGenParameterSpec("secp256r1")); 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 79902348a9..789faecb75 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 @@ -40,6 +40,7 @@ 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.TransportServiceCallback; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; @@ -106,21 +107,21 @@ public class LwM2mTransportServerHelper { /** * @return - sessionInfo after access connect client */ - public SessionInfoProto getValidateSessionInfo(TransportProtos.ValidateDeviceCredentialsResponseMsg msg, long mostSignificantBits, long leastSignificantBits) { + public SessionInfoProto getValidateSessionInfo(ValidateDeviceCredentialsResponse msg, long mostSignificantBits, long leastSignificantBits) { return SessionInfoProto.newBuilder() .setNodeId(context.getNodeId()) .setSessionIdMSB(mostSignificantBits) .setSessionIdLSB(leastSignificantBits) - .setDeviceIdMSB(msg.getDeviceInfo().getDeviceIdMSB()) - .setDeviceIdLSB(msg.getDeviceInfo().getDeviceIdLSB()) - .setTenantIdMSB(msg.getDeviceInfo().getTenantIdMSB()) - .setTenantIdLSB(msg.getDeviceInfo().getTenantIdLSB()) - .setCustomerIdMSB(msg.getDeviceInfo().getCustomerIdMSB()) - .setCustomerIdLSB(msg.getDeviceInfo().getCustomerIdLSB()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) + .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()) - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits()) .build(); } 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 691dd1d390..d20de6ebe4 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,6 +27,7 @@ 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.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; @@ -79,7 +80,7 @@ public class LwM2mClient implements Cloneable { @Setter private Registration registration; - private ValidateDeviceCredentialsResponseMsg credentialsResponse; + private ValidateDeviceCredentialsResponse credentials; @Getter private final Map resources; @Getter @@ -98,11 +99,11 @@ public class LwM2mClient implements Cloneable { return super.clone(); } - public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponseMsg credentialsResponse, UUID profileId, UUID sessionId) { + public LwM2mClient(String nodeId, String endpoint, String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID profileId, UUID sessionId) { this.endpoint = endpoint; this.identity = identity; this.securityInfo = securityInfo; - this.credentialsResponse = credentialsResponse; + this.credentials = credentials; this.delayedRequests = new ConcurrentHashMap<>(); this.pendingReadRequests = new CopyOnWriteArrayList<>(); this.resources = new ConcurrentHashMap<>(); @@ -112,8 +113,8 @@ 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); + if (this.credentials != null && this.credentials.hasDeviceInfo()) { + this.session = createSession(nodeId, sessionId, credentials); this.deviceId = new UUID(session.getDeviceIdMSB(), session.getDeviceIdLSB()); this.profileId = new UUID(session.getDeviceProfileIdMSB(), session.getDeviceProfileIdLSB()); this.deviceName = session.getDeviceName(); @@ -146,21 +147,21 @@ public class LwM2mClient implements Cloneable { builder.setDeviceType(this.deviceProfileName); } - private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponseMsg msg) { + private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponse 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()) + .setDeviceIdMSB(msg.getDeviceInfo().getDeviceId().getId().getMostSignificantBits()) + .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()) - .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileIdLSB()) - .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileIdMSB()) + .setDeviceProfileIdMSB(msg.getDeviceInfo().getDeviceProfileId().getId().getMostSignificantBits()) + .setDeviceProfileIdLSB(msg.getDeviceInfo().getDeviceProfileId().getId().getLeastSignificantBits()) .build(); } 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 4cdf6246e1..ce3bb7345e 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 @@ -79,7 +79,7 @@ public interface TransportService { TransportServiceCallback callback); void process(ValidateDeviceLwM2MCredentialsRequestMsg msg, - TransportServiceCallback callback); + TransportServiceCallback callback); void process(GetOrCreateDeviceFromGatewayRequestMsg msg, TransportServiceCallback callback); 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 69de33c8c0..b09b6bf223 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 @@ -348,11 +348,25 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg msg, TransportServiceCallback callback) { - log.trace("Processing msg: {}", msg); - TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(msg).build()); - AsyncCallbackTemplate.withCallback(transportApiRequestTemplate.send(protoMsg), - response -> callback.onSuccess(response.getValue().getValidateCredResponseMsg()), callback::onError, transportCallbackExecutor); + public void process(TransportProtos.ValidateDeviceLwM2MCredentialsRequestMsg requestMsg, TransportServiceCallback callback) { + log.trace("Processing msg: {}", requestMsg); + TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateDeviceLwM2MCredentialsRequestMsg(requestMsg).build()); + ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { + TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); + ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder(); + if (msg.hasDeviceInfo()) { + result.credentials(msg.getCredentialsBody()); + TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); + result.deviceInfo(tdi); + ByteString profileBody = msg.getProfileBody(); + if (!profileBody.isEmpty()) { + DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); + result.deviceProfile(profile); + } + } + return result.build(); + }, MoreExecutors.directExecutor()); + AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } @Override @@ -372,7 +386,7 @@ public class DefaultTransportService implements TransportService { TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); result.deviceInfo(tdi); ByteString profileBody = msg.getProfileBody(); - if (profileBody != null && !profileBody.isEmpty()) { + if (!profileBody.isEmpty()) { DeviceProfile profile = deviceProfileCache.getOrCreate(tdi.getDeviceProfileId(), profileBody); if (transportType != DeviceTransportType.DEFAULT && profile != null && profile.getTransportType() != DeviceTransportType.DEFAULT && profile.getTransportType() != transportType) { From 6dcde637ffdac711c89da3480ebadcc3c698796b Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Thu, 6 May 2021 18:16:57 +0800 Subject: [PATCH 156/476] Add some Simplified Chinese translation --- .../assets/locale/locale.constant-zh_CN.json | 182 ++++++++++++++---- 1 file changed, 143 insertions(+), 39 deletions(-) diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index b7a3078c3b..d436551d65 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -76,6 +76,7 @@ "general": "基本设置", "general-policy": "基本策略", "general-settings": "基本设置", + "home-settings": "首页设置", "mail-from": "邮件来自", "mail-from-required": "邮件发件人必填。", "max-failed-login-attempts": "登录失败之前的最大登录尝试次数", @@ -115,7 +116,7 @@ "customer-name-pattern": "客户名称模式", "default-dashboard-name": "默认仪表板名称", "delete-domain": "删除域", - "delete-domain-text": "注意,确认后域和所有 provider data 将不可用。", + "delete-domain-text": "请注意:确认后域和所有 provider data 将不可用。", "delete-domain-title": "确实要删除域 '{{domainName}}' 的设置吗?", "delete-provider": "删除 Provider", "delete-registration-text": "请注意:确认后 provider data 将不可用。", @@ -413,7 +414,7 @@ "assignedToCustomer": "分配客户", "copyId": "复制资产ID", "delete": "删除资产", - "delete-asset-text": "小心!确认后资产及其所有相关数据将不可恢复。", + "delete-asset-text": "请注意:确认后资产及其所有相关数据将不可恢复。", "delete-asset-title": "确定要删除资产 '{{assetName}}'?", "delete-assets": "删除资产", "delete-assets-action-title": "删除 { count, plural, 1 {# 个资产} other {# 个资产} }", @@ -589,10 +590,10 @@ "default-customer": "默认客户", "default-customer-required": "为了调试租户级别上的仪表板,需要默认客户。", "delete": "删除此客户", - "delete-customer-text": "小心!确认后,客户及其所有相关数据将不可恢复。", + "delete-customer-text": "请注意:确认后,客户及其所有相关数据将不可恢复。", "delete-customer-title": "您确定要删除客户'{{customerTitle}}'吗?", "delete-customers-action-title": "删除 { count, plural, 1 {# 个客户} other {# 个客户} }", - "delete-customers-text": "小心!确认后,所有选定的客户将被删除,所有相关数据将不可恢复。", + "delete-customers-text": "请注意:确认后,所有选定的客户将被删除,所有相关数据将不可恢复。", "delete-customers-title": "您确定要删除 { count, plural, 1 {# 个客户} other {# 个客户} }吗?", "description": "说明", "details": "详情", @@ -647,6 +648,7 @@ "background-color": "背景颜色", "background-image": "背景图片", "background-size-mode": "背景大小模式", + "cannot-upload-file": "无法上传文件", "close-toolbar": "关闭工具栏", "columns-count": "列数", "columns-count-required": "需要列数。", @@ -659,14 +661,15 @@ "dashboard-details": "仪表板详情", "dashboard-file": "仪表板文件", "dashboard-import-missing-aliases-title": "配置导入仪表板使用的别名", + "dashboard-logo-image": "仪表板 Logo 图片", "dashboard-required": "仪表板必填。", "dashboards": "仪表板库", "delete": "删除仪表板", - "delete-dashboard-text": "小心!确认后仪表板及其所有相关数据将不可恢复。", + "delete-dashboard-text": "请注意:确认后仪表板及其所有相关数据将不可恢复。", "delete-dashboard-title": "您确定要删除仪表板 '{{dashboardTitle}}'吗?", "delete-dashboards": "删除仪表板", "delete-dashboards-action-title": "删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", - "delete-dashboards-text": "小心!确认后所有选定的仪表板将被删除,所有相关数据将不可恢复。", + "delete-dashboards-text": "请注意:确认后所有选定的仪表板将被删除,所有相关数据将不可恢复。", "delete-dashboards-title": "确定要删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }吗?", "delete-state": "删除仪表板状态", "delete-state-text": "确定要删除仪表板状态 '{{stateName}}' 吗?", @@ -674,6 +677,7 @@ "description": "说明", "details": "详情", "display-dashboard-export": "显示导出", + "display-dashboard-logo": "在仪表板全屏模式下显示 Logo", "display-dashboard-timewindow": "显示时间窗口", "display-dashboards-selection": "显示仪表板选项", "display-entities-selection": "显示实体选项", @@ -684,6 +688,8 @@ "export": "导出仪表板", "export-failed-error": "无法导出仪表板: {{error}}", "hide-details": "隐藏详情", + "home-dashboard": "首页仪表板", + "home-dashboard-hide-toolbar": "隐藏首页仪表板工具栏", "horizontal-margin": "水平边距", "horizontal-margin-required": "需要水平边距值。", "import": "导入仪表板", @@ -736,6 +742,7 @@ "select-state": "选择目标状态", "select-widget-subtitle": "可用的部件类型列表", "select-widget-title": "选择部件", + "select-widget-value": "{{title}}: 选择部件", "selected-dashboards": "已选择 { count, plural, 1 {# 个仪表盘} other {# 个仪表盘} }", "selected-states": "已选择 { count, plural, 1 {# 个仪表板状态} other {# 个仪表板状态} }", "set-background": "设置背景", @@ -803,6 +810,7 @@ }, "datasource": { "add-datasource-prompt": "请添加数据源", + "label": "标签", "name": "名称", "type": "数据源类型" }, @@ -843,6 +851,11 @@ "attributes-topic-filter": "Attributes topic filter", "attributes-topic-filter-required": "Attributes topic 筛选器必填。", "clear-alarm-rule": "清除报警规则", + "coap-device-payload-type": "CoAP 设备消息 Payload", + "coap-device-type": "CoAP 设备类型", + "coap-device-type-default": "默认", + "coap-device-type-efento": "Efento NB-IoT", + "coap-device-type-required": "CoAP 设备类型必填。", "condition": "条件", "condition-duration": "条件持续时间", "condition-duration-time-unit": "时间单位", @@ -867,17 +880,19 @@ "copyId": "复制设备配置 ID", "create-alarm-pattern": "创建 {{alarmType}} 报警", "create-alarm-rules": "创建报警规则", + "create-device-profile": "创建设备配置", "create-new-device-profile": "创建一个新的!", "default": "默认", "default-rule-chain": "默认规则链", "delete": "删除设备配置", - "delete-device-profile-text": "注意,确认后设备配置和所有相关数据将不可恢复。", + "delete-device-profile-text": "请注意:确认后设备配置和所有相关数据将不可恢复。", "delete-device-profile-title": "是否确实要删除设备配置 '{{deviceProfileName}}'?", "delete-device-profiles-text": "请注意:确认后,所有选定的设备配置将被删除,所有相关数据将不可恢复。", "delete-device-profiles-title": "确定要删除 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }?", "description": "说明", "device-profile": "设备配置", "device-profile-details": "设备配置详情", + "device-profile-file": "设备配置文件", "device-profile-required": "设备配置必填", "device-profiles": "设备配置", "device-provisioning": "设备预配置", @@ -886,7 +901,11 @@ "edit-alarm-rule-condition": "编辑报警规则条件", "edit-schedule": "编辑报警日程表", "enter-alarm-rule-condition-prompt": "请添加报警规则条件", + "export": "导出设备配置", + "export-failed-error": "无法导出设备配置文件: {{error}}", "idCopiedMessage": "设备配置 ID 已复制到剪贴板", + "import": "导入设备配置", + "invalid-device-profile-file-error": "无法导入设备配置文件:无效的设备配置文件数据结构。", "mqtt-device-payload-type": "MQTT 设备 Payload", "mqtt-device-payload-type-json": "JSON", "mqtt-device-payload-type-proto": "Protobuf", @@ -920,6 +939,11 @@ "provision-strategy-created-new": "允许创建新设备", "provision-strategy-disabled": "禁用", "provision-strategy-required": "预配置策略必填。", + "rpc-request-proto-schema": "RPC 请求 proto schema", + "rpc-request-proto-schema-hint": "RPC 请求消息应始终包含字段:string method = 1; int32 requestId = 2; 和params = 3的任何数据类型。", + "rpc-request-proto-schema-required": "RPC 请求 proto schema 必填。", + "rpc-response-proto-schema": "RPC 响应 proto schema", + "rpc-response-proto-schema-required": "RPC 响应 proto schema 必填。", "rpc-response-topic-filter": "RPC响应 Topic 筛选器", "rpc-response-topic-filter-required": "RPC响应 Topic 筛选器必填。", "schedule": "启用规则:", @@ -957,6 +981,7 @@ "telemetry-topic-filter-required": "遥测数据 topic 筛选器必填。", "transport-configuration": "传输配置", "transport-type": "传输方式", + "transport-type-coap-hint": "启用高级 CoAP 传输设置", "transport-type-default": "默认", "transport-type-default-hint": "支持基本MQTT、HTTP和CoAP传输", "transport-type-lwm2m": "LWM2M", @@ -964,6 +989,7 @@ "transport-type-mqtt": "MQTT", "transport-type-mqtt-hint": "启用高级MQTT传输设置", "transport-type-required": "传输方式必填。", + "transport-type-snmp-hint": "指定 SNMP 传输配置", "type": "配置类型", "type-default": "默认", "type-required": "配置类型必填。" @@ -1000,11 +1026,11 @@ "credentials": "凭据", "credentials-type": "凭据类型", "delete": "删除设备", - "delete-device-text": "小心!确认后设备及其所有相关数据将不可恢复。", + "delete-device-text": "请注意:确认后设备及其所有相关数据将不可恢复。", "delete-device-title": "您确定要删除设备的{{deviceName}}吗?", "delete-devices": "删除设备", "delete-devices-action-title": "删除 {count,plural,1 {# 个设备} other {# 个设备} }", - "delete-devices-text": "小心!确认后所有选定的设备将被删除,所有相关数据将不可恢复。", + "delete-devices-text": "请注意:确认后所有选定的设备将被删除,所有相关数据将不可恢复。", "delete-devices-title": "确定要删除{count,plural,1 {# 个设备} other {# 个设备} } 吗?", "description": "说明", "details": "详情", @@ -1051,6 +1077,7 @@ "no-devices-text": "找不到设备", "no-key-matching": "'{{key}}' 没有找到。", "no-keys-found": "找不到密钥。", + "overwrite-activity-time": "覆盖已连接设备的活动时间", "password": "密码", "public": "公开", "remove-alias": "删除设备别名", @@ -1139,7 +1166,7 @@ "created-time": "创建时间", "date-limits": "日期限制", "delete": "删除实体视图", - "delete-entity-view-text": "小心!确认后实体视图及其所有相关数据将不可恢复。", + "delete-entity-view-text": "请注意:确认后实体视图及其所有相关数据将不可恢复。", "delete-entity-view-title": "确定要删除实体视图 '{{entityViewName}}'?", "delete-entity-views": "删除实体视图", "delete-entity-views-action-title": "删除 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }", @@ -1235,6 +1262,7 @@ "duplicate-alias-error": "别名 '{{alias}}' 重复。
同一仪表板别名必须唯一。", "enter-entity-type": "输入实体类型", "entities": "实体", + "entities-count": "实体数量", "entity": "实体", "entity-alias": "实体别名", "entity-label": "实体标签", @@ -1334,12 +1362,15 @@ "body": "整体", "data": "数据", "data-type": "数据类型", + "entity-id": "实体ID", "error": "错误", "errors-occurred": "错误发生", "event": "事件", "event-time": "事件时间", "event-type": "事件类型", + "events-filter": "事件筛选器", "failed": "失败", + "has-error": "有错误", "message-id": "消息ID", "message-type": "消息类型", "messages-processed": "消息处理", @@ -1552,6 +1583,7 @@ "key-name-required": "键名必填。", "key-type": { "attribute": "属性", + "constant": "常量", "entity-field": "实体", "key-type": "键类型", "timeseries": "Timeseries" @@ -1603,6 +1635,51 @@ "value-type": "值类型" } }, + "firmware": { + "add": "添加固件", + "checksum": "校验和", + "checksum-algorithm": "校验和算法", + "checksum-copied-message": "固件校验和已复制到剪贴板", + "checksum-required": "校验和为必填项。", + "content-type": "Content type", + "copy-checksum": "复制校验和", + "copyId": "复制固件ID", + "delete": "删除固件", + "delete-firmware-text": "请注意:确认后固件将无法恢复。", + "delete-firmware-title": "确定要删除固件'{{firmwareTitle}}'?", + "delete-firmwares-action-title": "Delete { count, plural, 1 {# 个固件} other {# 个固件} }", + "delete-firmwares-text": "请注意:确认后所有选定的资源都将被删除。", + "delete-firmwares-title": "确定要删除固件 { count, plural, 1 {# 个固件} other {# 个固件} }?", + "description": "描述", + "download": "下载固件", + "drop-file": "拖拽固件文件或单击以选择要上传的文件。", + "empty": "固件为空", + "file-name": "文件名", + "file-size": "文件大小", + "file-size-bytes": "文件大小(字节)", + "firmware": "固件", + "firmware-details": "固件详情", + "firmware-required": "固件为必选项。", + "idCopiedMessage": "固件ID已复制到剪贴板", + "no-firmware-matching": "找不到与 '{{entity}}' 匹配的固件。", + "no-firmware-text": "没有找到固件", + "no-software-matching": "找不到与 '{{entity}}' 匹配的软件。", + "no-software-text": "没有找到软件", + "search": "查找固件", + "selected-firmware": "已选择{ count, plural, 1 {# 个固件} other {# 个固件}", + "software": "软件", + "software-required": "软件为必选项。", + "title": "标题", + "title-required": "标题为必填项。", + "type": "固件类型", + "types": { + "firmware": "固件", + "software": "软件" + }, + "version": "版本号", + "version-required": "版本号为必填项。", + "warning-after-save-no-edit": "保存固件后,将无法更改标题和版本号。" +}, "fullscreen": { "exit": "退出全屏", "expand": "展开到全屏", @@ -1907,6 +1984,30 @@ "to-relations": "向内的关联", "type": "类型" }, + "resource": { + "add": "添加资源", + "delete": "删除资源", + "delete-resource-text": "请注意:确认后资源将不可恢复。", + "delete-resource-title": "确定要删除资源 '{{resourceTitle}}' 吗?", + "delete-resources-action-title": "删除 { count, plural, 1 {# 个资源} other {# 个资源} }", + "delete-resources-text": "请注意:确认后所有选定的资源都将被删除。", + "delete-resources-title": "确定要删除 { count, plural, 1 {# 个资源} other {# 个资源} }?", + "drop-file": "拖拽资源文件或单击以选择要上传的文件。", + "empty": "资源为空", + "export": "导出资源", + "no-resource-matching": "找不到与 '{{widgetsBundle}}' 匹配的资源。", + "no-resource-text": "找不到资源", + "open-widgets-bundle": "打开部件库", + "resource": "资源", + "resource-library-details": "资源库详情", + "resource-type": "资源类型", + "resources-library": "资源库", + "search": "查找资源", + "selected-resources": "已选择{ count, plural, 1 {# 个资源} other {# 个资源} }", + "system": "系统", + "title": "标题", + "title-required": "标题是必填项。" +}, "rulechain": { "add": "添加规则链", "add-rulechain-text": "添加新的规则链", @@ -2098,10 +2199,10 @@ "admins": "管理员", "copyId": "复制租户ID", "delete": "删除租户", - "delete-tenant-text": "小心!确认后,租户和所有相关数据将不可恢复。", + "delete-tenant-text": "请注意:确认后,租户和所有相关数据将不可恢复。", "delete-tenant-title": "您确定要删除租户'{{tenantTitle}}'吗?", "delete-tenants-action-title": "删除 { count, plural, 1 {# 个租户} other {# 个租户} }", - "delete-tenants-text": "小心!确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", + "delete-tenants-text": "请注意:确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", "delete-tenants-title": "确定要删除 {count,plural,1 {# 个租户} other {# 个租户} } 吗?", "description": "说明", "details": "详情", @@ -2178,10 +2279,10 @@ "customer-users": "客户用户", "default-dashboard": "默认面板", "delete": "删除用户", - "delete-user-text": "小心!确认后,用户和所有相关数据将不可恢复。", + "delete-user-text": "请注意:确认后,用户和所有相关数据将不可恢复。", "delete-user-title": "您确定要删除用户 '{{userEmail}}' 吗?", "delete-users-action-title": "删除 { count, plural, 1 {# 个用户} other {# 个用户} }", - "delete-users-text": "小心!确认后,所有选定的用户将被删除,所有相关数据将不可恢复。", + "delete-users-text": "请注意:确认后,所有选定的用户将被删除,所有相关数据将不可恢复。", "delete-users-title": "确定要删除 { count, plural, 1 {# 个用户} other {# 个用户} } 吗?", "description": "说明", "details": "详情", @@ -2343,11 +2444,11 @@ "run": "运行部件", "save": "保存部件", "save-widget-type-as": "部件类型另存为", - "save-widget-type-as-text": "请输入新的部件标题或选择目标部件组", + "save-widget-type-as-text": "请输入新的部件标题或选择目标部件包", "saveAs": "部件另存为", "search-data": "查找数据", "select-widget-type": "选择窗口部件类型", - "select-widgets-bundle": "选择部件组", + "select-widgets-bundle": "选择部件包", "settings-schema": "设置模式", "static": "静态部件", "tidy": "Tidy", @@ -2358,7 +2459,7 @@ "type": "部件类型", "unable-to-save-widget-error": "无法保存部件!控件有错误!", "undo": "撤消部件更改", - "widget-bundle": "部件组", + "widget-bundle": "部件包", "widget-library": "部件库", "widget-saved": "部件已保存", "widget-template-load-failed-error": "无法加载部件模板!", @@ -2367,33 +2468,36 @@ "widget-type-not-found": "加载部件配置出错。
可能关联的部件已经删除了。" }, "widgets-bundle": { - "add": "添加部件组", - "add-widgets-bundle-text": "添加新的部件组", - "create-new-widgets-bundle": "创建新的部件组", + "add": "添加部件包", + "add-widgets-bundle-text": "添加新的部件包", + "create-new-widgets-bundle": "创建新的部件包", "current": "当前组", - "delete": "删除部件组", - "delete-widgets-bundle-text": "小心!确认后,部件组和所有相关数据将不可恢复。", - "delete-widgets-bundle-title": "您确定要删除部件组 '{{widgetsBundleTitle}}'吗?", - "delete-widgets-bundles-action-title": "删除 { count, plural, 1 {# 个部件组} other {# 个部件组} }", - "delete-widgets-bundles-text": "小心!确认后,所有选定的部件组将被删除,所有相关数据将不可恢复。", - "delete-widgets-bundles-title": "确定要删除 { count, plural, 1 {# 个部件组} other {# 个部件组} } 吗?", + "delete": "删除部件包", + "delete-widgets-bundle-text": "请注意:确认后,部件包和所有相关数据将不可恢复。", + "delete-widgets-bundle-title": "您确定要删除部件包 '{{widgetsBundleTitle}}'吗?", + "delete-widgets-bundles-action-title": "删除 { count, plural, 1 {# 个部件包} other {# 个部件包} }", + "delete-widgets-bundles-text": "请注意:确认后,所有选定的部件包将被删除,所有相关数据将不可恢复。", + "delete-widgets-bundles-title": "确定要删除 { count, plural, 1 {# 个部件包} other {# 个部件包} } 吗?", + "description": "描述", "details": "详情", - "empty": "部件组是空的", - "export": "导出部件组", - "export-failed-error": "无法导出部件组: {{error}}", - "import": "导入部件组", - "invalid-widgets-bundle-file-error": "无法导入部件组:无效的部件组数据结构。", - "no-widgets-bundles-matching": "没有找到与 '{{widgetsBundle}}' 匹配的部件组。", - "no-widgets-bundles-text": "找不到部件组", - "open-widgets-bundle": "打开部件组", - "search": "查找部件组", - "selected-widgets-bundles": "已选择 { count, plural, 1 {# 个部件组} other {# 个部件组} }", + "empty": "部件包是空的", + "export": "导出部件包", + "export-failed-error": "无法导出部件包: {{error}}", + "image-preview": "图片预览", + "import": "导入部件包", + "invalid-widgets-bundle-file-error": "无法导入部件包:无效的部件包数据结构。", + "loading-widgets-bundles": "加载部件包...", + "no-widgets-bundles-matching": "没有找到与 '{{widgetsBundle}}' 匹配的部件包。", + "no-widgets-bundles-text": "找不到部件包", + "open-widgets-bundle": "打开部件包", + "search": "查找部件包", + "selected-widgets-bundles": "已选择 { count, plural, 1 {# 个部件包} other {# 个部件包} }", "system": "系统", "title": "标题", "title-required": "标题必填。", - "widgets-bundle-details": "部件组详细信息", - "widgets-bundle-file": "部件组文件", - "widgets-bundle-required": "部件组必填。", + "widgets-bundle-details": "部件包详细信息", + "widgets-bundle-file": "部件包文件", + "widgets-bundle-required": "部件包必填。", "widgets-bundles": "部件包" }, "widgets": { From a4eb2df8eee8aecccbccfbb27f7e27f19d8fa0fc Mon Sep 17 00:00:00 2001 From: zbeacon Date: Thu, 6 May 2021 13:46:04 +0300 Subject: [PATCH 157/476] Fix for device software/firmware response topic --- .../server/common/data/device/profile/MqttTopics.java | 3 ++- .../server/transport/mqtt/adaptors/JsonMqttAdaptor.java | 4 ++-- .../server/transport/mqtt/adaptors/ProtoMqttAdaptor.java | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) 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 6373d41803..2b1aba3c0f 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 @@ -82,7 +82,8 @@ public class MqttTopics { 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_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; 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 dbda48d15d..a9d7b3e2ea 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 @@ -44,7 +44,7 @@ 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; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT; /** @@ -156,7 +156,7 @@ public class JsonMqttAdaptor implements MqttTransportAdaptor { @Override 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)); + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_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/ProtoMqttAdaptor.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/adaptors/ProtoMqttAdaptor.java index 29df08e9c3..08a2f9abe3 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 @@ -39,7 +39,7 @@ 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; +import static org.thingsboard.server.common.data.device.profile.MqttTopics.DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT; @Component @Slf4j @@ -169,7 +169,7 @@ public class ProtoMqttAdaptor implements MqttTransportAdaptor { @Override 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)); + return Optional.of(createMqttPublishMsg(ctx, String.format(DEVICE_SOFTWARE_FIRMWARE_RESPONSES_TOPIC_FORMAT, firmwareType.getKeyPrefix(), requestId, chunk), firmwareChunk)); } @Override From 38af4d5d2da99804c2aebe829e9c6aeb9b786701 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 13:48:09 +0300 Subject: [PATCH 158/476] Implementation of custom L2M2M Authorizer --- .../lwm2m/secure/TbLwM2MAuthorizer.java | 41 ++++++++++++++++++- .../TbLwM2MDtlsCertificateVerifier.java | 31 +++++++++----- ...torage.java => TbX509DtlsSessionInfo.java} | 9 ++-- .../server/client/LwM2mClientContext.java | 3 ++ .../server/client/LwM2mClientContextImpl.java | 8 ++++ .../TbL2M2MDtlsSessionInMemoryStore.java | 39 ++++++++++++++++++ .../server/store/TbLwM2MDtlsSessionStore.java | 29 +++++++++++++ .../server/store/TbLwM2mSecurityStore.java | 4 ++ 8 files changed, 149 insertions(+), 15 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/{TbLwM2MDtlsSessionStorage.java => TbX509DtlsSessionInfo.java} (81%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java index 3719ce7246..0236f6d276 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MAuthorizer.java @@ -15,14 +15,53 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import lombok.RequiredArgsConstructor; import org.eclipse.leshan.core.request.Identity; import org.eclipse.leshan.core.request.UplinkRequest; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.Authorizer; +import org.eclipse.leshan.server.security.SecurityChecker; +import org.eclipse.leshan.server.security.SecurityInfo; +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2mSecurityStore; +@Component +@RequiredArgsConstructor +@TbLwM2mTransportComponent public class TbLwM2MAuthorizer implements Authorizer { + + private final TbLwM2MDtlsSessionStore sessionStorage; + private final TbLwM2mSecurityStore securityStore; + private final SecurityChecker securityChecker = new SecurityChecker(); + private final LwM2mClientContext clientContext; + @Override public Registration isAuthorized(UplinkRequest request, Registration registration, Identity senderIdentity) { - return null; + if (senderIdentity.isX509()) { + TbX509DtlsSessionInfo sessionInfo = sessionStorage.get(registration.getEndpoint()); + if (sessionInfo != null) { + if (senderIdentity.getX509CommonName().equals(sessionInfo.getX509CommonName())) { + clientContext.registerClient(registration, sessionInfo.getCredentials()); + // X509 certificate is valid and matches endpoint. + return registration; + } else { + // X509 certificate is not valid. + return null; + } + } + // If session info is not found, this may be the trusted certificate, so we still need to check all other options below. + } + SecurityInfo expectedSecurityInfo = null; + if (securityStore != null) { + expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); + } + if (securityChecker.checkSecurityInfo(registration.getEndpoint(), senderIdentity, expectedSecurityInfo)) { + return registration; + } else { + return null; + } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index c3d9d85d04..57fa8a7169 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.CertPathUtil; @@ -32,6 +33,7 @@ import org.eclipse.californium.scandium.util.ServerNames; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; @@ -40,6 +42,7 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; import javax.security.auth.x500.X500Principal; @@ -60,7 +63,7 @@ import java.util.concurrent.TimeUnit; public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVerifier { private final TransportService transportService; - private final TbLwM2MDtlsSessionStorage sessionStorage; + private final TbLwM2MDtlsSessionStore sessionStorage; private final LwM2MTransportServerConfig config; @SuppressWarnings("deprecation") @@ -130,16 +133,24 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer latch.countDown(); } }); - latch.await(10, TimeUnit.SECONDS); - ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; - if (msg != null && strCert.equals(msg.getCredentials())) { - credentialsBody = msg.getCredentials(); - DeviceProfile deviceProfile = msg.getDeviceProfile(); - if (msg.hasDeviceInfo() && deviceProfile != null) { - String endpoint = sha3Hash; //TODO: extract endpoint from credentials body and push to storage - sessionStorage.put(endpoint, msg); + if (latch.await(10, TimeUnit.SECONDS)) { + ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; + if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { + JsonNode credentialsJson = JacksonUtil.toJsonNode(msg.getCredentials()); + String certBody = credentialsJson.get("cert").asText(); + String endpoint = credentialsJson.get("endpoint").asText(); + if (strCert.equals(certBody)) { + //TODO: extract endpoint from credentials body and push to storage + credentialsBody = msg.getCredentials(); + DeviceProfile deviceProfile = msg.getDeviceProfile(); + if (msg.hasDeviceInfo() && deviceProfile != null) { + sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); + break; + } + } else { + log.trace("[{}][{}] Certificate mismatch. Expected: {}, Actual: {}", endpoint, sha3Hash, strCert, certBody); + } } - break; } } catch (InterruptedException | CertificateEncodingException | diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java similarity index 81% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java index 26c38a6327..1c038a9440 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsSessionStorage.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java @@ -15,12 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.secure; +import lombok.Data; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -public interface TbLwM2MDtlsSessionStorage { +@Data +public class TbX509DtlsSessionInfo { - void put(String endpoint, ValidateDeviceCredentialsResponse msg); - - ValidateDeviceCredentialsResponse get(String endpoint); + private final String x509CommonName; + private final ValidateDeviceCredentialsResponse credentials; } 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 8e7f6a70ec..4aa8db59b6 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Collection; @@ -59,4 +60,6 @@ public interface LwM2mClientContext { Set getSupportedIdVerInClient(Registration registration); LwM2mClient getClientByDeviceId(UUID deviceId); + + void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); } 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 3a7b652ab3..b8583523d5 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 @@ -21,6 +21,7 @@ import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.security.EditableSecurityStore; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; @@ -136,6 +137,13 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } } + @Override + public void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials) { + LwM2mClient client = new LwM2mClient(context.getNodeId(), registration.getEndpoint(), null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID()); + lwM2mClientsByEndpoint.put(registration.getEndpoint(), client); + lwM2mClientsByRegistrationId.put(registration.getId(), client); + } + @Override public Collection getLwM2mClients() { return lwM2mClientsByEndpoint.values(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.java new file mode 100644 index 0000000000..b71b7b12c1 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbL2M2MDtlsSessionInMemoryStore.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.transport.lwm2m.server.store; + +import org.springframework.stereotype.Component; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; + +import java.util.concurrent.ConcurrentHashMap; + +@Component +@TbLwM2mTransportComponent +public class TbL2M2MDtlsSessionInMemoryStore implements TbLwM2MDtlsSessionStore { + + private final ConcurrentHashMap store = new ConcurrentHashMap<>(); + + @Override + public void put(String endpoint, TbX509DtlsSessionInfo msg) { + store.put(endpoint, msg); + } + + @Override + public TbX509DtlsSessionInfo get(String endpoint) { + return store.get(endpoint); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.java new file mode 100644 index 0000000000..bc4ddff7ac --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionStore.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.transport.lwm2m.server.store; + + +import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; + +public interface TbLwM2MDtlsSessionStore { + + void put(String endpoint, TbX509DtlsSessionInfo msg); + + TbX509DtlsSessionInfo get(String endpoint); + + //TODO: add way to delete the session by endpoint. + +} 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 index e6ed99c5e6..512790036f 100644 --- 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 @@ -20,12 +20,16 @@ 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.springframework.stereotype.Component; +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; @Slf4j +@Component +@TbLwM2mTransportComponent public class TbLwM2mSecurityStore implements EditableSecurityStore { private final LwM2mClientContext clientContext; From 7ca626a0868422018a8489dce417b02ee31ef4c5 Mon Sep 17 00:00:00 2001 From: Yevhen Bondarenko <56396344+YevhenBondarenko@users.noreply.github.com> Date: Thu, 6 May 2021 13:49:51 +0300 Subject: [PATCH 159/476] Created LwM2M credentials (#4546) * Created LwM2M credentials * psk endpoint --- .../server/controller/Lwm2mController.java | 5 +- .../LwM2MServerSecurityInfoRepository.java | 11 +- .../secure/LwM2MBootstrapConfig.java | 12 +- .../secure/LwM2MBootstrapSecurityStore.java | 20 ++-- .../secure/LwM2MServerBootstrap.java | 2 +- .../lwm2m/secure/EndpointSecurityInfo.java | 10 +- .../secure/LWM2MGenerationPSkRPkECC.java | 22 +--- .../lwm2m/secure/LwM2MSecurityMode.java | 58 --------- ...LwM2mCredentialsSecurityInfoValidator.java | 111 ++++++++---------- .../lwm2m/secure/credentials/HasKey.java | 17 +++ .../LwM2MClientCredentialsConfig.java | 22 ++++ .../secure/credentials/LwM2MCredentials.java | 10 ++ .../NoSecClientCredentialsConfig.java | 13 ++ .../PSKClientCredentialsConfig.java | 17 +++ .../RPKClientCredentialsConfig.java | 13 ++ .../X509ClientCredentialsConfig.java | 17 +++ .../server/client/LwM2mClientContextImpl.java | 7 +- 17 files changed, 199 insertions(+), 168 deletions(-) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java 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 0855e4ee1f..9e6d393b30 100644 --- a/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java +++ b/application/src/main/java/org/thingsboard/server/controller/Lwm2mController.java @@ -17,6 +17,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; @@ -46,9 +47,11 @@ public class Lwm2mController extends BaseController { @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/lwm2m/deviceProfile/bootstrap/{securityMode}/{bootstrapServerIs}", method = RequestMethod.GET) @ResponseBody - public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String securityMode, + public ServerSecurityConfig getLwm2mBootstrapSecurityInfo(@PathVariable("securityMode") String strSecurityMode, @PathVariable("bootstrapServerIs") boolean bootstrapServer) throws ThingsboardException { + checkNotNull(strSecurityMode); try { + SecurityMode securityMode = SecurityMode.valueOf(strSecurityMode); return lwM2MServerSecurityInfoRepository.getServerSecurityInfo(securityMode, bootstrapServer); } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java index 012cd3e359..06190cdf70 100644 --- a/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/lwm2m/LwM2MServerSecurityInfoRepository.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.lwm2m; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.Hex; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; @@ -25,7 +26,6 @@ import org.thingsboard.server.common.data.lwm2m.ServerSecurityConfig; 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; import java.security.AlgorithmParameters; @@ -55,17 +55,16 @@ public class LwM2MServerSecurityInfoRepository { * @param bootstrapServer * @return ServerSecurityConfig more value is default: Important - port, host, publicKey */ - public ServerSecurityConfig getServerSecurityInfo(String securityMode, boolean bootstrapServer) { - LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(securityMode.toLowerCase()); - ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, lwM2MSecurityMode); + public ServerSecurityConfig getServerSecurityInfo(SecurityMode securityMode, boolean bootstrapServer) { + ServerSecurityConfig result = getServerSecurityConfig(bootstrapServer ? bootstrapConfig : serverConfig, securityMode); result.setBootstrapServerIs(bootstrapServer); return result; } - private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, LwM2MSecurityMode mode) { + private ServerSecurityConfig getServerSecurityConfig(LwM2MSecureServerConfig serverConfig, SecurityMode securityMode) { ServerSecurityConfig bsServ = new ServerSecurityConfig(); bsServ.setServerId(serverConfig.getId()); - switch (mode) { + switch (securityMode) { case NO_SEC: bsServ.setHost(serverConfig.getHost()); bsServ.setPort(serverConfig.getPort()); 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 937257b189..2f175a6bce 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 @@ -73,17 +73,17 @@ public class LwM2MBootstrapConfig { configBs.servers.put(0, server0); /* 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())); + configBs.security.put(0, setServerSecurity(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 */ - 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())); + configBs.security.put(1, setServerSecurity(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; } - private BootstrapConfig.ServerSecurity setServerSecuruty(String host, Integer port, boolean bootstrapServer, String securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { + private BootstrapConfig.ServerSecurity setServerSecurity(String host, Integer port, boolean bootstrapServer, SecurityMode securityMode, String clientPublicKey, String serverPublicKey, String secretKey, int serverId) { BootstrapConfig.ServerSecurity serverSecurity = new BootstrapConfig.ServerSecurity(); serverSecurity.uri = "coaps://" + host + ":" + Integer.toString(port); serverSecurity.bootstrapServer = bootstrapServer; - serverSecurity.securityMode = SecurityMode.valueOf(securityMode); + serverSecurity.securityMode = securityMode; serverSecurity.publicKeyOrId = setPublicKeyOrId(clientPublicKey, securityMode); serverSecurity.serverPublicKey = (serverPublicKey != null && !serverPublicKey.isEmpty()) ? Hex.decodeHex(serverPublicKey.toCharArray()) : new byte[]{}; serverSecurity.secretKey = (secretKey != null && !secretKey.isEmpty()) ? Hex.decodeHex(secretKey.toCharArray()) : new byte[]{}; @@ -91,9 +91,9 @@ public class LwM2MBootstrapConfig { return serverSecurity; } - private byte[] setPublicKeyOrId(String publicKeyOrIdStr, String securityMode) { + private byte[] setPublicKeyOrId(String publicKeyOrIdStr, SecurityMode securityMode) { return (publicKeyOrIdStr == null || publicKeyOrIdStr.isEmpty()) ? new byte[]{} : - SecurityMode.valueOf(securityMode).equals(SecurityMode.PSK) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) : + SecurityMode.PSK.equals(securityMode) ? publicKeyOrIdStr.getBytes(StandardCharsets.UTF_8) : Hex.decodeHex(publicKeyOrIdStr.toCharArray()); } } 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 22e0540c09..b1edd0a1a5 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 @@ -31,7 +31,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; @@ -73,7 +72,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public List getAllByEndpoint(String endPoint) { EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); - if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfigNew = store.getBootstrapConfig(); @@ -97,7 +96,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { EndpointSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); - if (store.getBootstrapJsonCredential() != null && store.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); BootstrapConfig bsConfig = store.getBootstrapConfig(); @@ -118,29 +117,29 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); if (lwM2MBootstrapConfig != null) { /* Security info */ - switch (SecurityMode.valueOf(lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode())) { + switch (lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode()) { /* Use RPK only */ case PSK: store.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(store.getEndpoint(), lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId(), Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientSecretKey().toCharArray()))); - store.setSecurityMode(SecurityMode.PSK.code); + store.setSecurityMode(SecurityMode.PSK); break; case RPK: try { store.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(store.getEndpoint(), SecurityUtil.publicKey.decode(Hex.decodeHex(lwM2MBootstrapConfig.getBootstrapServer().getClientPublicKeyOrId().toCharArray())))); - store.setSecurityMode(SecurityMode.RPK.code); + store.setSecurityMode(SecurityMode.RPK); break; } catch (IOException | GeneralSecurityException e) { log.error("Unable to decode Client public key for [{}] [{}]", store.getEndpoint(), e.getMessage()); } case X509: store.setSecurityInfo(SecurityInfo.newX509CertInfo(store.getEndpoint())); - store.setSecurityMode(SecurityMode.X509.code); + store.setSecurityMode(SecurityMode.X509); break; case NO_SEC: - store.setSecurityMode(SecurityMode.NO_SEC.code); + store.setSecurityMode(SecurityMode.NO_SEC); store.setSecurityInfo(null); break; default: @@ -152,10 +151,9 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { private LwM2MBootstrapConfig getParametersBootstrap(EndpointSecurityInfo store) { try { - JsonObject bootstrapJsonCredential = store.getBootstrapJsonCredential(); - if (bootstrapJsonCredential != null) { + LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); + if (lwM2MBootstrapConfig != null) { ObjectMapper mapper = new ObjectMapper(); - LwM2MBootstrapConfig lwM2MBootstrapConfig = mapper.readValue(bootstrapJsonCredential.toString(), LwM2MBootstrapConfig.class); JsonObject bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); lwM2MBootstrapConfig.servers = mapper.readValue(bootstrapObject.get(SERVERS).toString(), LwM2MBootstrapServers.class); LwM2MServerBootstrap profileServerBootstrap = mapper.readValue(bootstrapObject.get(BOOTSTRAP_SERVER).toString(), LwM2MServerBootstrap.class); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java index 9dca6057da..27d2e8c865 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MServerBootstrap.java @@ -32,7 +32,7 @@ public class LwM2MServerBootstrap { String host = "0.0.0.0"; Integer port = 0; - String securityMode = SecurityMode.NO_SEC.name(); + SecurityMode securityMode = SecurityMode.NO_SEC; Integer serverId = 123; boolean bootstrapServerIs = false; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java index f65acf04bd..e8d3ae3c2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/EndpointSecurityInfo.java @@ -15,25 +15,23 @@ */ package org.thingsboard.server.transport.lwm2m.secure; -import com.google.gson.JsonObject; import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; import org.eclipse.leshan.server.security.SecurityInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; - -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.DEFAULT_MODE; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; @Data public class EndpointSecurityInfo { private ValidateDeviceCredentialsResponse msg; private SecurityInfo securityInfo; - private int securityMode = DEFAULT_MODE.code; + private SecurityMode securityMode; /** bootstrap */ private DeviceProfile deviceProfile; - private JsonObject bootstrapJsonCredential; + private LwM2MBootstrapConfig bootstrapCredentialConfig; private String endpoint; private BootstrapConfig bootstrapConfig; } 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 b99192ece3..22c5878a58 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 @@ -33,16 +33,6 @@ import java.util.Arrays; @Slf4j public class LWM2MGenerationPSkRPkECC { - public LWM2MGenerationPSkRPkECC(Integer dtlsMode) { - switch (LwM2MSecurityMode.fromSecurityMode(dtlsMode)) { - case PSK: - generationPSkKey(); - break; - case RPK: - generationRPKECCKey(); - } - } - public LWM2MGenerationPSkRPkECC() { generationPSkKey(); generationRPKECCKey(); @@ -102,12 +92,12 @@ public class LWM2MGenerationPSkRPkECC { /* Get Curves params */ String privHex = Hex.encodeHexString(privKey.getEncoded()); log.info("\nCreating new RPK for the next start... \n" + - " Public Key (Hex): [{}]\n" + - " Private Key (Hex): [{}]" + - " public_x : [{}] \n" + - " public_y : [{}] \n" + - " private_encode : [{}] \n" + - " Elliptic Curve parameters : [{}] \n", + " Public Key (Hex): [{}]\n" + + " Private Key (Hex): [{}]" + + " public_x : [{}] \n" + + " public_y : [{}] \n" + + " private_encode : [{}] \n" + + " Elliptic Curve parameters : [{}] \n", Hex.encodeHexString(pubKey.getEncoded()), privHex, Hex.encodeHexString(x), diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java deleted file mode 100644 index faf776b76c..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2MSecurityMode.java +++ /dev/null @@ -1,58 +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.secure; - -public enum LwM2MSecurityMode { - - PSK(0, "psk"), - RPK(1, "rpk"), - X509(2, "x509"), - NO_SEC(3, "no_sec"), - X509_EST(4, "x509_est"), - REDIS(7, "redis"), - DEFAULT_MODE(255, "default_mode"); - - public int code; - public String subEndpoint; - - LwM2MSecurityMode(int code, String subEndpoint) { - this.code = code; - this.subEndpoint = subEndpoint; - } - - public static LwM2MSecurityMode fromSecurityMode(long code) { - return fromSecurityMode((int) code); - } - - public static LwM2MSecurityMode fromSecurityMode(int code) { - for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) { - if (sm.code == code) { - return sm; - } - } - throw new IllegalArgumentException(String.format("Unsupported security code : %d", code)); - } - - - public static LwM2MSecurityMode fromSecurityMode(String subEndpoint) { - for (LwM2MSecurityMode sm : LwM2MSecurityMode.values()) { - if (sm.subEndpoint.equals(subEndpoint)) { - return sm; - } - } - throw new IllegalArgumentException(String.format("Unsupported security subEndpoint : %d", subEndpoint)); - } -} 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 ec5200602c..cd78cd4c1e 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 @@ -15,34 +15,36 @@ */ 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.SecurityMode; import org.eclipse.leshan.core.util.SecurityUtil; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -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.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MClientCredentialsConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.PSKClientCredentialsConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.RPKClientCredentialsConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.io.IOException; import java.security.GeneralSecurityException; import java.security.PublicKey; -import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.PSK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.RPK; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.X509; +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; +import static org.eclipse.leshan.core.SecurityMode.PSK; +import static org.eclipse.leshan.core.SecurityMode.RPK; +import static org.eclipse.leshan.core.SecurityMode.X509; @Slf4j @Component @@ -53,7 +55,6 @@ public class LwM2mCredentialsSecurityInfoValidator { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - public EndpointSecurityInfo getEndpointSecurityInfo(String endpoint, LwM2mTransportUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final EndpointSecurityInfo[] resultSecurityStore = new EndpointSecurityInfo[1]; @@ -92,39 +93,32 @@ public class LwM2mCredentialsSecurityInfoValidator { */ 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") - && 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; - if (object != null && !object.isJsonNull()) { - 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); - } else { - LwM2MSecurityMode lwM2MSecurityMode = LwM2MSecurityMode.fromSecurityMode(object.get("securityConfigClientMode").getAsString().toLowerCase()); - switch (lwM2MSecurityMode) { - case NO_SEC: - createClientSecurityInfoNoSec(result); - break; - case PSK: - createClientSecurityInfoPSK(result, endpoint, object); - break; - case RPK: - createClientSecurityInfoRPK(result, endpoint, object); - break; - case X509: - createClientSecurityInfoX509(result, endpoint); - break; - default: - break; - } + LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class); + if (credentials != null) { + if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { + result.setBootstrapCredentialConfig(credentials.getBootstrap()); + if (SecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { + PSKClientCredentialsConfig pskClientConfig = (PSKClientCredentialsConfig) credentials.getClient(); + endpoint = StringUtils.isNotEmpty(pskClientConfig.getEndpoint()) ? pskClientConfig.getEndpoint() : endpoint; + } + result.setEndpoint(endpoint); + result.setSecurityMode(credentials.getBootstrap().getBootstrapServer().getSecurityMode()); + } else { + switch (credentials.getClient().getSecurityConfigClientMode()) { + case NO_SEC: + createClientSecurityInfoNoSec(result); + break; + case PSK: + createClientSecurityInfoPSK(result, endpoint, credentials.getClient()); + break; + case RPK: + createClientSecurityInfoRPK(result, endpoint, credentials.getClient()); + break; + case X509: + createClientSecurityInfoX509(result, endpoint, credentials.getClient()); + break; + default: + break; } } } @@ -133,19 +127,18 @@ public class LwM2mCredentialsSecurityInfoValidator { private void createClientSecurityInfoNoSec(EndpointSecurityInfo result) { result.setSecurityInfo(null); - result.setSecurityMode(NO_SEC.code); + result.setSecurityMode(NO_SEC); } - 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()) { + private void createClientSecurityInfoPSK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { + PSKClientCredentialsConfig pskConfig = (PSKClientCredentialsConfig) clientCredentialsConfig; + if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { 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 (pskConfig.getKey() != null && pskConfig.getKey().length > 0) { + endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint; if (endpoint != null && !endpoint.isEmpty()) { - result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, identity, key)); - result.setSecurityMode(PSK.code); + result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getKey())); + result.setSecurityMode(PSK); } } } catch (IllegalArgumentException e) { @@ -156,13 +149,13 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, JsonObject object) { + private void createClientSecurityInfoRPK(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { + RPKClientCredentialsConfig rpkConfig = (RPKClientCredentialsConfig) clientCredentialsConfig; try { - if (object.has("key") && object.get("key").isJsonPrimitive()) { - byte[] rpkkey = Hex.decodeHex(object.get("key").getAsString().toLowerCase().toCharArray()); - PublicKey key = SecurityUtil.publicKey.decode(rpkkey); + if (rpkConfig.getKey() != null) { + PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getKey()); result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key)); - result.setSecurityMode(RPK.code); + result.setSecurityMode(RPK); } else { log.error("Missing RPK key"); } @@ -171,8 +164,8 @@ public class LwM2mCredentialsSecurityInfoValidator { } } - private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint) { + private void createClientSecurityInfoX509(EndpointSecurityInfo result, String endpoint, LwM2MClientCredentialsConfig clientCredentialsConfig) { result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); - result.setSecurityMode(X509.code); + result.setSecurityMode(X509); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java new file mode 100644 index 0000000000..7c75589c4c --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.util.Hex; + +public class HasKey { + private byte[] key; + + public void setKey(String key) { + if (key != null) { + this.key = Hex.decodeHex(key.toLowerCase().toCharArray()); + } + } + + public byte[] getKey() { + return key; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java new file mode 100644 index 0000000000..33a297c91a --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java @@ -0,0 +1,22 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +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.eclipse.leshan.core.SecurityMode; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "securityConfigClientMode") +@JsonSubTypes({ + @JsonSubTypes.Type(value = NoSecClientCredentialsConfig.class, name = "NO_SEC"), + @JsonSubTypes.Type(value = PSKClientCredentialsConfig.class, name = "PSK"), + @JsonSubTypes.Type(value = RPKClientCredentialsConfig.class, name = "RPK"), + @JsonSubTypes.Type(value = X509ClientCredentialsConfig.class, name = "X509")}) +public interface LwM2MClientCredentialsConfig { + + @JsonIgnore + SecurityMode getSecurityConfigClientMode(); +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java new file mode 100644 index 0000000000..ebb1fc138b --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java @@ -0,0 +1,10 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; + +@Data +public class LwM2MCredentials { + private LwM2MClientCredentialsConfig client; + private LwM2MBootstrapConfig bootstrap; +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java new file mode 100644 index 0000000000..bcac7174aa --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java @@ -0,0 +1,13 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; + +public class NoSecClientCredentialsConfig implements LwM2MClientCredentialsConfig { + + @Override + public SecurityMode getSecurityConfigClientMode() { + return NO_SEC; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java new file mode 100644 index 0000000000..d79083647f --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.PSK; + +@Data +public class PSKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { + private String identity; + private String endpoint; + + @Override + public SecurityMode getSecurityConfigClientMode() { + return PSK; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java new file mode 100644 index 0000000000..280e8492f8 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java @@ -0,0 +1,13 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.RPK; + +public class RPKClientCredentialsConfig extends HasKey implements LwM2MClientCredentialsConfig { + + @Override + public SecurityMode getSecurityConfigClientMode() { + return RPK; + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java new file mode 100644 index 0000000000..35fe63c376 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java @@ -0,0 +1,17 @@ +package org.thingsboard.server.transport.lwm2m.secure.credentials; + +import lombok.Data; +import org.eclipse.leshan.core.SecurityMode; + +import static org.eclipse.leshan.core.SecurityMode.X509; + +@Data +public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { + private boolean allowTrustedOnly; + private String cert; + + @Override + public SecurityMode getSecurityConfigClientMode() { + return X509; + } +} 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 b8583523d5..02ef1ccfb2 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,6 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.EndpointSecurityInfo; -import org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -38,7 +37,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import static org.thingsboard.server.transport.lwm2m.secure.LwM2MSecurityMode.NO_SEC; +import static org.eclipse.leshan.core.SecurityMode.NO_SEC; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @Service @@ -111,7 +110,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public LwM2mClient fetchClientByEndpoint(String endpoint) { EndpointSecurityInfo securityInfo = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfo(endpoint, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); - if (securityInfo.getSecurityMode() < LwM2MSecurityMode.DEFAULT_MODE.code) { + if (securityInfo.getSecurityMode() != null) { if (securityInfo.getDeviceProfile() != null) { toClientProfile(securityInfo.getDeviceProfile()); UUID profileUuid = securityInfo.getDeviceProfile().getUuidId(); @@ -120,7 +119,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { 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) { + } else if (NO_SEC.equals(securityInfo.getSecurityMode())) { client = new LwM2mClient(context.getNodeId(), endpoint, null, null, securityInfo.getMsg(), profileUuid, UUID.randomUUID()); From 38843c839c18c294c8526bfc665e7b5f59333cc8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 14:07:29 +0300 Subject: [PATCH 160/476] Merge with the new data structures --- .../TbLwM2MDtlsCertificateVerifier.java | 20 ++++++++++++------- .../X509ClientCredentialsConfig.java | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index 57fa8a7169..d2542192bf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -30,6 +30,7 @@ import org.eclipse.californium.scandium.dtls.HandshakeResultHandler; import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier; import org.eclipse.californium.scandium.dtls.x509.StaticCertificateVerifier; import org.eclipse.californium.scandium.util.ServerNames; +import org.eclipse.leshan.core.SecurityMode; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -42,6 +43,8 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.X509ClientCredentialsConfig; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import javax.annotation.PostConstruct; @@ -104,7 +107,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer return new CertificateVerificationResult(cid, publicKey, null); } else { try { - String credentialsBody = null; + boolean x509CredentialsFound = false; CertPath certpath = message.getCertificateChain(); X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); for (X509Certificate cert : chain) { @@ -136,12 +139,15 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer if (latch.await(10, TimeUnit.SECONDS)) { ValidateDeviceCredentialsResponse msg = deviceCredentialsResponse[0]; if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { - JsonNode credentialsJson = JacksonUtil.toJsonNode(msg.getCredentials()); - String certBody = credentialsJson.get("cert").asText(); - String endpoint = credentialsJson.get("endpoint").asText(); + LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); + if(!credentials.getClient().getSecurityConfigClientMode().equals(SecurityMode.X509)){ + continue; + } + X509ClientCredentialsConfig config = (X509ClientCredentialsConfig) credentials.getClient(); + String certBody = config.getCert(); + String endpoint = config.getEndpoint(); if (strCert.equals(certBody)) { - //TODO: extract endpoint from credentials body and push to storage - credentialsBody = msg.getCredentials(); + x509CredentialsFound = true; DeviceProfile deviceProfile = msg.getDeviceProfile(); if (msg.hasDeviceInfo() && deviceProfile != null) { sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); @@ -159,7 +165,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer log.error(e.getMessage(), e); } } - if (credentialsBody == null) { + if (!x509CredentialsFound) { if (staticCertificateVerifier != null) { staticCertificateVerifier.verifyCertificate(message, session); } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java index 35fe63c376..0aff74d189 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java @@ -9,6 +9,7 @@ import static org.eclipse.leshan.core.SecurityMode.X509; public class X509ClientCredentialsConfig implements LwM2MClientCredentialsConfig { private boolean allowTrustedOnly; private String cert; + private String endpoint; @Override public SecurityMode getSecurityConfigClientMode() { From d5b640d6021d4a3b57ae1b70cb1706084809a0af Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 6 May 2021 17:01:46 +0300 Subject: [PATCH 161/476] Add image fields for dashboard and device profile entities. Introduce getAllAlarms and getCustomerAlarms API --- .../main/data/upgrade/3.2.2/schema_update.sql | 4 ++ .../server/controller/AlarmController.java | 35 ++++++++++ .../DefaultAlarmSubscriptionService.java | 5 ++ .../BaseDeviceProfileControllerTest.java | 2 +- .../server/dao/alarm/AlarmService.java | 2 + .../server/common/data/DashboardInfo.java | 10 +++ .../server/common/data/DeviceProfile.java | 2 + .../server/common/data/DeviceProfileInfo.java | 6 +- .../server/dao/alarm/AlarmDao.java | 2 + .../server/dao/alarm/BaseAlarmService.java | 43 ++++++++----- .../server/dao/model/ModelConstants.java | 2 + .../server/dao/model/sql/DashboardEntity.java | 7 +- .../dao/model/sql/DashboardInfoEntity.java | 5 ++ .../dao/model/sql/DeviceProfileEntity.java | 5 ++ .../server/dao/sql/alarm/AlarmRepository.java | 64 +++++++++++++++++++ .../server/dao/sql/alarm/JpaAlarmDao.java | 41 +++++++++++- .../sql/device/DeviceProfileRepository.java | 8 +-- .../resources/sql/schema-entities-hsql.sql | 4 +- .../main/resources/sql/schema-entities.sql | 4 +- .../service/BaseDeviceProfileServiceTest.java | 2 +- .../engine/api/RuleEngineAlarmService.java | 2 + .../add-device-profile-dialog.component.html | 5 ++ .../add-device-profile-dialog.component.ts | 2 + .../profile/device-profile.component.html | 5 ++ .../profile/device-profile.component.ts | 2 + .../dashboard/dashboard-form.component.html | 5 ++ .../dashboard/dashboard-form.component.ts | 2 + .../components/image-input.component.html | 8 +-- .../src/app/shared/models/dashboard.models.ts | 1 + ui-ngx/src/app/shared/models/device.models.ts | 1 + .../assets/locale/locale.constant-en_US.json | 3 + 31 files changed, 257 insertions(+), 32 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 af3fd418a8..4bb63f3b9f 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 @@ -78,7 +78,11 @@ CREATE TABLE IF NOT EXISTS firmware ( CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) ); +ALTER TABLE dashboard + ADD COLUMN IF NOT EXISTS image varchar(1000000); + ALTER TABLE device_profile + ADD COLUMN IF NOT EXISTS image varchar(1000000), ADD COLUMN IF NOT EXISTS firmware_id uuid, ADD COLUMN IF NOT EXISTS software_id uuid; 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 9453bd46b7..6aec756f61 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -196,6 +196,41 @@ public class AlarmController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/alarms", method = RequestMethod.GET) + @ResponseBody + public PageData getAllAlarms( + @RequestParam(required = false) String searchStatus, + @RequestParam(required = false) String status, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder, + @RequestParam(required = false) Long startTime, + @RequestParam(required = false) Long endTime, + @RequestParam(required = false) Boolean fetchOriginator + ) throws ThingsboardException { + accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, Operation.READ); + AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus); + AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status); + if (alarmSearchStatus != null && alarmStatus != null) { + throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); + + try { + if (getCurrentUser().isCustomerUser()) { + return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); + } else { + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); + } + } catch (Exception e) { + throw handleException(e); + } + } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET) @ResponseBody 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 c2ff292667..82700ff064 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 @@ -127,6 +127,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService return alarmService.findAlarms(tenantId, query); } + @Override + public ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) { + return alarmService.findCustomerAlarms(tenantId, customerId, query); + } + @Override public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) { return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index 310fb40215..95a878f9db 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -313,7 +313,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController Collections.sort(loadedDeviceProfileInfos, deviceProfileInfoIdComparator); List deviceProfileInfos = deviceProfiles.stream().map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(), - deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); + deviceProfile.getName(), deviceProfile.getImage(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 5badd872df..2bca57b1b0 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -53,6 +53,8 @@ public interface AlarmService { ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); + ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); + AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java index 854c5ff64b..cbcd213377 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java @@ -30,6 +30,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa private TenantId tenantId; @NoXss private String title; + private String image; @Valid private Set assignedCustomers; @@ -45,6 +46,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa super(dashboardInfo); this.tenantId = dashboardInfo.getTenantId(); this.title = dashboardInfo.getTitle(); + this.image = dashboardInfo.getImage(); this.assignedCustomers = dashboardInfo.getAssignedCustomers(); } @@ -64,6 +66,14 @@ public class DashboardInfo extends SearchTextBased implements HasNa this.title = title; } + public String getImage() { + return image; + } + + public void setImage(String image) { + this.image = image; + } + public Set getAssignedCustomers() { return assignedCustomers; } 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 daf7d77af0..8104f6bf80 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 @@ -43,6 +43,7 @@ public class DeviceProfile extends SearchTextBased implements H private String name; @NoXss private String description; + private String image; private boolean isDefault; private DeviceProfileType type; private DeviceTransportType transportType; @@ -74,6 +75,7 @@ public class DeviceProfile extends SearchTextBased implements H this.tenantId = deviceProfile.getTenantId(); this.name = deviceProfile.getName(); this.description = deviceProfile.getDescription(); + this.image = deviceProfile.getImage(); this.isDefault = deviceProfile.isDefault(); this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); this.defaultQueueName = deviceProfile.getDefaultQueueName(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java index db83c58d13..c134934d66 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfileInfo.java @@ -30,21 +30,25 @@ import java.util.UUID; @ToString(callSuper = true) public class DeviceProfileInfo extends EntityInfo { + private final String image; private final DeviceProfileType type; private final DeviceTransportType transportType; @JsonCreator public DeviceProfileInfo(@JsonProperty("id") EntityId id, @JsonProperty("name") String name, + @JsonProperty("image") String image, @JsonProperty("type") DeviceProfileType type, @JsonProperty("transportType") DeviceTransportType transportType) { super(id, name); + this.image = image; this.type = type; this.transportType = transportType; } - public DeviceProfileInfo(UUID uuid, String name, DeviceProfileType type, DeviceTransportType transportType) { + public DeviceProfileInfo(UUID uuid, String name, String image, DeviceProfileType type, DeviceTransportType transportType) { super(EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE_PROFILE, uuid), name); + this.image = image; this.type = type; this.transportType = transportType; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index ebbfae0f05..eb873db679 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -48,6 +48,8 @@ public interface AlarmDao extends Dao { PageData findAlarms(TenantId tenantId, AlarmQuery query); + PageData findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); + PageData findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection orderedEntityIds); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 9dff668f2e..472cbb588f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -292,25 +292,38 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { PageData alarms = alarmDao.findAlarms(tenantId, query); if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { - List> alarmFutures = new ArrayList<>(alarms.getData().size()); - for (AlarmInfo alarmInfo : alarms.getData()) { - alarmFutures.add(Futures.transform( - entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { - if (originatorName == null) { - originatorName = "Deleted"; - } - alarmInfo.setOriginatorName(originatorName); - return alarmInfo; - }, MoreExecutors.directExecutor() - )); - } - return Futures.transform(Futures.successfulAsList(alarmFutures), - alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), - alarms.hasNext()), MoreExecutors.directExecutor()); + return fetchAlarmsOriginators(tenantId, alarms); } return Futures.immediateFuture(alarms); } + @Override + public ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) { + PageData alarms = alarmDao.findCustomerAlarms(tenantId, customerId, query); + if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { + return fetchAlarmsOriginators(tenantId, alarms); + } + return Futures.immediateFuture(alarms); + } + + private ListenableFuture> fetchAlarmsOriginators(TenantId tenantId, PageData alarms) { + List> alarmFutures = new ArrayList<>(alarms.getData().size()); + for (AlarmInfo alarmInfo : alarms.getData()) { + alarmFutures.add(Futures.transform( + entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { + if (originatorName == null) { + originatorName = "Deleted"; + } + alarmInfo.setOriginatorName(originatorName); + return alarmInfo; + }, MoreExecutors.directExecutor() + )); + } + return Futures.transform(Futures.successfulAsList(alarmFutures), + alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), + alarms.hasNext()), MoreExecutors.directExecutor()); + } + @Override public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) { 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 bc0afe630d..5b4ea874ee 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 @@ -170,6 +170,7 @@ public class ModelConstants { public static final String DEVICE_PROFILE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String DEVICE_PROFILE_NAME_PROPERTY = "name"; public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type"; + public static final String DEVICE_PROFILE_IMAGE_PROPERTY = "image"; public static final String DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY = "transport_type"; public static final String DEVICE_PROFILE_PROVISION_TYPE_PROPERTY = "provision_type"; public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data"; @@ -333,6 +334,7 @@ public class ModelConstants { public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard"; public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY; + public static final String DASHBOARD_IMAGE_PROPERTY = "image"; public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration"; public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java index e0780a5fc9..8e499858bc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardEntity.java @@ -58,7 +58,10 @@ public final class DashboardEntity extends BaseSqlEntity implements S @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) private String title; - + + @Column(name = ModelConstants.DASHBOARD_IMAGE_PROPERTY) + private String image; + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; @@ -82,6 +85,7 @@ public final class DashboardEntity extends BaseSqlEntity implements S this.tenantId = dashboard.getTenantId().getId(); } this.title = dashboard.getTitle(); + this.image = dashboard.getImage(); if (dashboard.getAssignedCustomers() != null) { try { this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers()); @@ -110,6 +114,7 @@ public final class DashboardEntity extends BaseSqlEntity implements S dashboard.setTenantId(new TenantId(tenantId)); } dashboard.setTitle(title); + dashboard.setImage(image); if (!StringUtils.isEmpty(assignedCustomers)) { try { dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java index 47c7016aba..b3d7738843 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DashboardInfoEntity.java @@ -54,6 +54,9 @@ public class DashboardInfoEntity extends BaseSqlEntity implements @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) private String title; + @Column(name = ModelConstants.DASHBOARD_IMAGE_PROPERTY) + private String image; + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) private String searchText; @@ -73,6 +76,7 @@ public class DashboardInfoEntity extends BaseSqlEntity implements this.tenantId = dashboardInfo.getTenantId().getId(); } this.title = dashboardInfo.getTitle(); + this.image = dashboardInfo.getImage(); if (dashboardInfo.getAssignedCustomers() != null) { try { this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers()); @@ -104,6 +108,7 @@ public class DashboardInfoEntity extends BaseSqlEntity implements dashboardInfo.setTenantId(new TenantId(tenantId)); } dashboardInfo.setTitle(title); + dashboardInfo.setImage(image); if (!StringUtils.isEmpty(assignedCustomers)) { try { dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); 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 e4c32a44f0..f66153abbc 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 @@ -60,6 +60,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_TYPE_PROPERTY) private DeviceProfileType type; + @Column(name = ModelConstants.DEVICE_PROFILE_IMAGE_PROPERTY) + private String image; + @Enumerated(EnumType.STRING) @Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY) private DeviceTransportType transportType; @@ -110,6 +113,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl this.setCreatedTime(deviceProfile.getCreatedTime()); this.name = deviceProfile.getName(); this.type = deviceProfile.getType(); + this.image = deviceProfile.getImage(); this.transportType = deviceProfile.getTransportType(); this.provisionType = deviceProfile.getProvisionType(); this.description = deviceProfile.getDescription(); @@ -151,6 +155,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl } deviceProfile.setName(name); deviceProfile.setType(type); + deviceProfile.setImage(image); deviceProfile.setTransportType(transportType); deviceProfile.setProvisionType(provisionType); deviceProfile.setDescription(description); 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 446125e4fb..028eda1583 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 @@ -94,6 +94,70 @@ public interface AlarmRepository extends CrudRepository { @Param("searchText") String searchText, Pageable pageable); + @Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " + + "WHERE a.tenantId = :tenantId " + + "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 " + + "WHERE a.tenantId = :tenantId " + + "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, '%'))) ") + Page findAllAlarms(@Param("tenantId") UUID tenantId, + @Param("startTime") Long startTime, + @Param("endTime") Long endTime, + @Param("alarmStatuses") Set alarmStatuses, + @Param("searchText") String searchText, + Pageable pageable); + + @Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " + + "WHERE a.tenantId = :tenantId " + + "AND (" + + "a.originatorId IN (SELECT d.id from DeviceEntity d WHERE d.customerId = :customerId) " + + "OR a.originatorId IN (SELECT asset.id from AssetEntity asset WHERE asset.customerId = :customerId) " + + "OR a.originatorId IN (SELECT u.id from UserEntity u WHERE u.customerId = :customerId) " + + "OR a.originatorId = :customerId" + + ") " + + "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 " + + "WHERE a.tenantId = :tenantId " + + "AND (" + + "a.originatorId IN (SELECT d.id from DeviceEntity d WHERE d.customerId = :customerId) " + + "OR a.originatorId IN (SELECT asset.id from AssetEntity asset WHERE asset.customerId = :customerId) " + + "OR a.originatorId IN (SELECT u.id from UserEntity u WHERE u.customerId = :customerId) " + + "OR a.originatorId = :customerId" + + ") " + + "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, '%'))) ") + Page findCustomerAlarms(@Param("tenantId") UUID tenantId, + @Param("customerId") UUID customerId, + @Param("startTime") Long startTime, + @Param("endTime") Long endTime, + @Param("alarmStatuses") Set alarmStatuses, + @Param("searchText") String searchText, + Pageable pageable); + @Query(value = "SELECT a.severity FROM AlarmEntity a " + "LEFT JOIN RelationEntity re ON a.id = re.toId " + "AND re.relationTypeGroup = 'ALARM' " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index bfca088c69..cf222413b0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java @@ -103,11 +103,46 @@ public class JpaAlarmDao extends JpaAbstractDao implements A } else if (query.getStatus() != null) { statusSet = Collections.singleton(query.getStatus()); } + if (affectedEntity != null) { + return DaoUtil.toPageData( + alarmRepository.findAlarms( + tenantId.getId(), + affectedEntity.getId(), + affectedEntity.getEntityType().name(), + query.getPageLink().getStartTime(), + query.getPageLink().getEndTime(), + statusSet, + Objects.toString(query.getPageLink().getTextSearch(), ""), + DaoUtil.toPageable(query.getPageLink()) + ) + ); + } else { + return DaoUtil.toPageData( + alarmRepository.findAllAlarms( + tenantId.getId(), + query.getPageLink().getStartTime(), + query.getPageLink().getEndTime(), + statusSet, + Objects.toString(query.getPageLink().getTextSearch(), ""), + DaoUtil.toPageable(query.getPageLink()) + ) + ); + } + } + + @Override + public PageData findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) { + log.trace("Try to find customer alarms by status [{}] and pageLink [{}]", query.getStatus(), query.getPageLink()); + Set statusSet = null; + if (query.getSearchStatus() != null) { + statusSet = query.getSearchStatus().getStatuses(); + } else if (query.getStatus() != null) { + statusSet = Collections.singleton(query.getStatus()); + } return DaoUtil.toPageData( - alarmRepository.findAlarms( + alarmRepository.findCustomerAlarms( tenantId.getId(), - affectedEntity.getId(), - affectedEntity.getEntityType().name(), + customerId.getId(), query.getPageLink().getStartTime(), query.getPageLink().getEndTime(), statusSet, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java index c4d6db691b..33d1c3ece1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceProfileRepository.java @@ -28,7 +28,7 @@ import java.util.UUID; public interface DeviceProfileRepository extends PagingAndSortingRepository { - @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.type, d.transportType) " + "FROM DeviceProfileEntity d " + "WHERE d.id = :deviceProfileId") DeviceProfileInfo findDeviceProfileInfoById(@Param("deviceProfileId") UUID deviceProfileId); @@ -39,14 +39,14 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository findDeviceProfileInfos(@Param("tenantId") UUID tenantId, @Param("textSearch") String textSearch, Pageable pageable); - @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.type, d.transportType) " + "FROM DeviceProfileEntity d WHERE " + "d.tenantId = :tenantId AND d.transportType = :transportType AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") Page findDeviceProfileInfos(@Param("tenantId") UUID tenantId, @@ -58,7 +58,7 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository deviceProfileInfos = deviceProfiles.stream() .map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(), - deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); + deviceProfile.getName(), deviceProfile.getImage(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java index d6a0a3e576..7d3157588b 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java @@ -57,6 +57,8 @@ public interface RuleEngineAlarmService { ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); + ListenableFuture> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); + AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus); PageData findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection orderedEntityIds); 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..fa9b03bcb3 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,11 @@ {{ '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 9ad7993857..1de74c9de5 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 @@ -106,6 +106,7 @@ export class AddDeviceProfileDialogComponent extends { name: [data.deviceProfileName, [Validators.required]], type: [DeviceProfileType.DEFAULT, [Validators.required]], + image: [null, []], defaultRuleChainId: [null, []], defaultQueueName: ['', []], description: ['', []] @@ -183,6 +184,7 @@ export class AddDeviceProfileDialogComponent extends const deviceProfile: DeviceProfile = { name: this.deviceProfileDetailsFormGroup.get('name').value, type: this.deviceProfileDetailsFormGroup.get('type').value, + image: this.deviceProfileDetailsFormGroup.get('image').value, transportType: this.transportConfigFormGroup.get('transportType').value, provisionType: deviceProvisionConfiguration.type, provisionDeviceKey, 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 5c21d65adc..cff6177955 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 @@ -86,6 +86,11 @@ {{ 'device-profile.type-required' | translate }} + + device-profile.description 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 d8505a0a3b..1e8699210d 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 @@ -103,6 +103,7 @@ export class DeviceProfileComponent extends EntityComponent { { name: [entity ? entity.name : '', [Validators.required]], type: [entity ? entity.type : null, [Validators.required]], + image: [entity ? entity.image : null], transportType: [entity ? entity.transportType : null, [Validators.required]], profileData: this.fb.group({ configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required], @@ -178,6 +179,7 @@ export class DeviceProfileComponent extends EntityComponent { }; this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); + this.entityForm.patchValue({image: entity.image}, {emitEvent: false}); this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false}); this.entityForm.patchValue({provisionType: entity.provisionType}, {emitEvent: false}); this.entityForm.patchValue({provisionDeviceKey: entity.provisionDeviceKey}, {emitEvent: false}); diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html index 1fb8978f22..ca7512d7ec 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html @@ -105,6 +105,11 @@ {{ 'dashboard.title-required' | translate }} + +
dashboard.description diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts index 37030c57f1..70914e069d 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.ts @@ -80,6 +80,7 @@ export class DashboardFormComponent extends EntityComponent { return this.fb.group( { title: [entity ? entity.title : '', [Validators.required]], + image: [entity ? entity.image : null], configuration: this.fb.group( { description: [entity && entity.configuration ? entity.configuration.description : ''], @@ -92,6 +93,7 @@ export class DashboardFormComponent extends EntityComponent { updateForm(entity: Dashboard) { this.updateFields(entity); this.entityForm.patchValue({title: entity.title}); + this.entityForm.patchValue({image: entity.image}); this.entityForm.patchValue({configuration: {description: entity.configuration ? entity.configuration.description : ''}}); } diff --git a/ui-ngx/src/app/shared/components/image-input.component.html b/ui-ngx/src/app/shared/components/image-input.component.html index 4b7e4fe1d4..2fe8cc4102 100644 --- a/ui-ngx/src/app/shared/components/image-input.component.html +++ b/ui-ngx/src/app/shared/components/image-input.component.html @@ -21,10 +21,10 @@ [flowConfig]="{singleFile: true, allowDuplicateUploads: true}">
-
dashboard.no-image
+
{{ (disabled ? 'dashboard.empty-image' : 'dashboard.no-image') | translate }}
-
+
-
@@ -42,5 +42,5 @@
-
dashboard.maximum-upload-file-size
+
dashboard.maximum-upload-file-size
diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 6a90101923..98a19d1b13 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -26,6 +26,7 @@ import { Filters } from '@shared/models/query/query.models'; export interface DashboardInfo extends BaseData { tenantId?: TenantId; title?: string; + image?: string; assignedCustomers?: Array; } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 683d9ddcc1..81fdb7a8fd 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -492,6 +492,7 @@ export interface DeviceProfile extends BaseData { description?: string; default?: boolean; type: DeviceProfileType; + image?: string; transportType: DeviceTransportType; provisionType: DeviceProvisionType; provisionDeviceKey?: string; 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 f664dc27a7..adfd8434a2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -666,6 +666,7 @@ "no-widgets": "No widgets configured", "add-widget": "Add new widget", "title": "Title", + "image": "Dashboard image", "select-widget-title": "Select widget", "select-widget-value": "{{title}}: select widget", "select-widget-subtitle": "List of available widget types", @@ -712,6 +713,7 @@ "background-image": "Background image", "background-size-mode": "Background size mode", "no-image": "No image selected", + "empty-image": "No image", "drop-image": "Drop an image or click to select a file to upload.", "maximum-upload-file-size": "Maximum upload file size: {{ size }}", "cannot-upload-file": "Cannot upload file", @@ -1028,6 +1030,7 @@ "type": "Profile type", "type-required": "Profile type is required.", "type-default": "Default", + "image": "Device profile image", "transport-type": "Transport type", "transport-type-required": "Transport type is required.", "transport-type-default": "Default", From 4ec25beeb3a109277260018163832eaa320a27b9 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 6 May 2021 17:11:24 +0300 Subject: [PATCH 162/476] LwM2M Integration test --- .../server/controller/AbstractWebTest.java | 3 +- .../transport/TransportSqlTestSuite.java | 3 +- .../lwm2m/AbstractLwM2MIntegrationTest.java | 95 ++++ .../lwm2m/NoSecLwM2MIntegrationTest.java | 156 +++++++ .../lwm2m/client/LwM2MTestClient.java | 262 +++++++++++ .../lwm2m/client/SimpleLwM2MDevice.java | 199 +++++++++ application/src/test/resources/logback.xml | 6 +- application/src/test/resources/lwm2m/0.xml | 405 ++++++++++++++++++ application/src/test/resources/lwm2m/1.xml | 360 ++++++++++++++++ application/src/test/resources/lwm2m/2.xml | 123 ++++++ application/src/test/resources/lwm2m/3.xml | 331 ++++++++++++++ .../lwm2m/secure/credentials/HasKey.java | 15 + .../LwM2MClientCredentialsConfig.java | 15 + .../secure/credentials/LwM2MCredentials.java | 15 + .../NoSecClientCredentialsConfig.java | 15 + .../PSKClientCredentialsConfig.java | 15 + .../RPKClientCredentialsConfig.java | 15 + .../X509ClientCredentialsConfig.java | 15 + .../DefaultLwM2MTransportMsgHandler.java | 3 +- 19 files changed, 2045 insertions(+), 6 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java create mode 100644 application/src/test/resources/lwm2m/0.xml create mode 100644 application/src/test/resources/lwm2m/1.xml create mode 100644 application/src/test/resources/lwm2m/2.xml create mode 100644 application/src/test/resources/lwm2m/3.xml diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 2329e8086c..937244fc93 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -22,6 +22,7 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwts; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; @@ -120,7 +121,7 @@ public abstract class AbstractWebTest { protected String refreshToken; protected String username; - private TenantId tenantId; + protected TenantId tenantId; @SuppressWarnings("rawtypes") private HttpMessageConverter mappingJackson2HttpMessageConverter; diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index d16bbc3885..25df3bee00 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -32,7 +32,8 @@ import java.util.Arrays; "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", "org.thingsboard.server.transport.*.attributes.request.sql.*Test", "org.thingsboard.server.transport.*.claim.sql.*Test", - "org.thingsboard.server.transport.*.provision.sql.*Test" + "org.thingsboard.server.transport.*.provision.sql.*Test", + "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java new file mode 100644 index 0000000000..97f7bfbc3c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -0,0 +1,95 @@ +/** + * 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; + +import com.fasterxml.jackson.core.type.TypeReference; +import org.apache.commons.io.IOUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.thingsboard.common.util.JacksonUtil; +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.ResourceType; +import org.thingsboard.server.common.data.TbResource; +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.DisabledDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.controller.AbstractWebsocketTest; +import org.thingsboard.server.controller.TbTestWebSocketClient; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Base64; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +@DaoSqlTest +public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { + + protected DeviceProfile deviceProfile; + protected ScheduledExecutorService executor; + protected TbTestWebSocketClient wsClient; + + @Before + public void beforeTest() throws Exception { + executor = Executors.newScheduledThreadPool(10); + loginTenantAdmin(); + + String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; + for (String resourceName : resources) { + TbResource lwModel = new TbResource(); + lwModel.setResourceType(ResourceType.LWM2M_MODEL); + lwModel.setTitle(resourceName); + lwModel.setFileName(resourceName); + lwModel.setTenantId(tenantId); + byte[] bytes = IOUtils.toByteArray(AbstractLwM2MIntegrationTest.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName)); + lwModel.setData(Base64.getEncoder().encodeToString(bytes)); + lwModel = doPostWithTypedResponse("/api/resource", lwModel, new TypeReference<>(){}); + Assert.assertNotNull(lwModel); + } + wsClient = buildAndConnectWebSocketClient(); + } + + protected void createDeviceProfile(String transportConfiguration) throws Exception { + deviceProfile = new DeviceProfile(); + + deviceProfile.setName("LwM2M No Security"); + deviceProfile.setType(DeviceProfileType.DEFAULT); + deviceProfile.setTenantId(tenantId); + deviceProfile.setTransportType(DeviceTransportType.LWM2M); + deviceProfile.setProvisionType(DeviceProfileProvisionType.DISABLED); + deviceProfile.setDescription(deviceProfile.getName()); + + DeviceProfileData deviceProfileData = new DeviceProfileData(); + deviceProfileData.setConfiguration(new DefaultDeviceProfileConfiguration()); + deviceProfileData.setProvisionConfiguration(new DisabledDeviceProfileProvisionConfiguration(null)); + deviceProfileData.setTransportConfiguration(JacksonUtil.fromString(transportConfiguration, Lwm2mDeviceProfileTransportConfiguration.class)); + deviceProfile.setProfileData(deviceProfileData); + + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertNotNull(deviceProfile); + } + + @After + public void after() { + executor.shutdownNow(); + wsClient.close(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java new file mode 100644 index 0000000000..809f1f609b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -0,0 +1,156 @@ +/** + * 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; + +import org.jetbrains.annotations.NotNull; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityDataQuery; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.SingleEntityFilter; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; +import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; +import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd; +import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; +import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; +import org.thingsboard.server.transport.lwm2m.secure.credentials.NoSecClientCredentialsConfig; + +import java.util.Collections; +import java.util.List; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + + protected final String TRANSPORT_CONFIGURATION = "{\n" + + " \"type\": \"LWM2M\",\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"/3_1.0/0/9\": \"batteryLevel\"\n" + + " },\n" + + " \"observe\": [],\n" + + " \"attribute\": [\n" + + " ],\n" + + " \"telemetry\": [\n" + + " \"/3_1.0/0/9\"\n" + + " ],\n" + + " \"attributeLwm2m\": {}\n" + + " },\n" + + " \"bootstrap\": {\n" + + " \"servers\": {\n" + + " \"binding\": \"UQ\",\n" + + " \"shortId\": 123,\n" + + " \"lifetime\": 300,\n" + + " \"notifIfDisabled\": true,\n" + + " \"defaultMinPeriod\": 1\n" + + " },\n" + + " \"lwm2mServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5685,\n" + + " \"serverId\": 123,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": false,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " },\n" + + " \"bootstrapServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5687,\n" + + " \"serverId\": 111,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"serverPublicKey\": \"\",\n" + + " \"bootstrapServerIs\": true,\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " }\n" + + " },\n" + + " \"clientLwM2mSettings\": {\n" + + " \"clientOnlyObserveAfterConnect\": 1\n" + + " }\n" + + "}"; + + @NotNull + private Device createDevice(String deviceAEndpoint) throws Exception { + Device device = new Device(); + device.setName("Device A"); + device.setDeviceProfileId(deviceProfile.getId()); + device.setTenantId(tenantId); + device = doPost("/api/device", device, Device.class); + Assert.assertNotNull(device); + + DeviceCredentials deviceCredentials = + doGet("/api/device/" + device.getId().getId().toString() + "/credentials", DeviceCredentials.class); + Assert.assertEquals(device.getId(), deviceCredentials.getDeviceId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS); + + deviceCredentials.setCredentialsId(deviceAEndpoint); + + LwM2MCredentials noSecCredentials = new LwM2MCredentials(); + noSecCredentials.setClient(new NoSecClientCredentialsConfig()); + deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); + return device; + } + + @Test + public void testConnectAndObserveTelemetry() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + + String deviceAEndpoint = "deviceAEndpoint"; + + Device device = createDevice(deviceAEndpoint); + + SingleEntityFilter sef = new SingleEntityFilter(); + sef.setSingleEntity(device.getId()); + LatestValueCmd latestCmd = new LatestValueCmd(); + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "batteryLevel"))); + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + + wsClient.send(mapper.writeValueAsString(wrapper)); + wsClient.waitForReply(); + + wsClient.registerWaitForUpdate(); + LwM2MTestClient client = new LwM2MTestClient(executor, deviceAEndpoint); + client.init(); + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List eData = update.getUpdate(); + Assert.assertNotNull(eData); + Assert.assertEquals(1, eData.size()); + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("batteryLevel"); + Assert.assertEquals(42, Long.parseLong(tsValue.getValue())); + client.destroy(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java new file mode 100644 index 0000000000..6061813bcf --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java @@ -0,0 +1,262 @@ +/** + * 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 lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.californium.elements.Connector; +import org.eclipse.californium.scandium.DTLSConnector; +import org.eclipse.californium.scandium.config.DtlsConnectorConfig; +import org.eclipse.californium.scandium.dtls.ClientHandshaker; +import org.eclipse.californium.scandium.dtls.DTLSSession; +import org.eclipse.californium.scandium.dtls.HandshakeException; +import org.eclipse.californium.scandium.dtls.Handshaker; +import org.eclipse.californium.scandium.dtls.ResumingClientHandshaker; +import org.eclipse.californium.scandium.dtls.ResumingServerHandshaker; +import org.eclipse.californium.scandium.dtls.ServerHandshaker; +import org.eclipse.californium.scandium.dtls.SessionAdapter; +import org.eclipse.leshan.client.californium.LeshanClient; +import org.eclipse.leshan.client.californium.LeshanClientBuilder; +import org.eclipse.leshan.client.engine.DefaultRegistrationEngineFactory; +import org.eclipse.leshan.client.object.Server; +import org.eclipse.leshan.client.observer.LwM2mClientObserver; +import org.eclipse.leshan.client.resource.ObjectsInitializer; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.californium.DefaultEndpointFactory; +import org.eclipse.leshan.core.model.LwM2mModel; +import org.eclipse.leshan.core.model.ObjectLoader; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.StaticModel; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; +import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeEncoder; +import org.eclipse.leshan.core.request.BindingMode; +import org.eclipse.leshan.core.request.BootstrapRequest; +import org.eclipse.leshan.core.request.DeregisterRequest; +import org.eclipse.leshan.core.request.RegisterRequest; +import org.eclipse.leshan.core.request.UpdateRequest; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; + +import static org.eclipse.leshan.client.object.Security.noSec; +import static org.eclipse.leshan.core.LwM2mId.DEVICE; +import static org.eclipse.leshan.core.LwM2mId.SECURITY; +import static org.eclipse.leshan.core.LwM2mId.SERVER; + +@Slf4j +@Data +public class LwM2MTestClient { + + private final ScheduledExecutorService executor; + private final String endpoint; + private LeshanClient client; + + public void init() { + String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; + List models = new ArrayList<>(); + for (String resourceName : resources) { + models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); + } + LwM2mModel model = new StaticModel(models); + ObjectsInitializer initializer = new ObjectsInitializer(model); + initializer.setInstancesForObject(SECURITY, noSec("coap://localhost:5685", 123)); + initializer.setInstancesForObject(SERVER, new Server(123, 300, BindingMode.U, false)); + initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); + + NetworkConfig coapConfig = new NetworkConfig(); + coapConfig.setString("COAP_PORT", Integer.toString(5685)); + + DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); + dtlsConfig.setRecommendedCipherSuitesOnly(true); + + DefaultRegistrationEngineFactory engineFactory = new DefaultRegistrationEngineFactory(); + engineFactory.setReconnectOnUpdate(false); + engineFactory.setResumeOnConnect(true); + + DefaultEndpointFactory endpointFactory = new DefaultEndpointFactory(endpoint) { + @Override + protected Connector createSecuredConnector(DtlsConnectorConfig dtlsConfig) { + + return new DTLSConnector(dtlsConfig) { + @Override + protected void onInitializeHandshaker(Handshaker handshaker) { + handshaker.addSessionListener(new SessionAdapter() { + + @Override + public void handshakeStarted(Handshaker handshaker) throws HandshakeException { + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : STARTED ..."); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : STARTED ..."); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : STARTED ..."); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : STARTED ..."); + } + } + + @Override + public void sessionEstablished(Handshaker handshaker, DTLSSession establishedSession) + throws HandshakeException { + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : SUCCEED, handshaker {}", handshaker); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : SUCCEED, handshaker {}", handshaker); + } + } + + @Override + public void handshakeFailed(Handshaker handshaker, Throwable error) { + /** get cause */ + String cause; + if (error != null) { + if (error.getMessage() != null) { + cause = error.getMessage(); + } else { + cause = error.getClass().getName(); + } + } else { + cause = "unknown cause"; + } + + if (handshaker instanceof ServerHandshaker) { + log.info("DTLS Full Handshake initiated by server : FAILED [{}]", cause); + } else if (handshaker instanceof ResumingServerHandshaker) { + log.info("DTLS abbreviated Handshake initiated by server : FAILED [{}]", cause); + } else if (handshaker instanceof ClientHandshaker) { + log.info("DTLS Full Handshake initiated by client : FAILED [{}]", cause); + } else if (handshaker instanceof ResumingClientHandshaker) { + log.info("DTLS abbreviated Handshake initiated by client : FAILED [{}]", cause); + } + } + }); + } + }; + } + }; + + LeshanClientBuilder builder = new LeshanClientBuilder(endpoint); + builder.setLocalAddress("0.0.0.0", 11000); + builder.setObjects(initializer.createAll()); + builder.setCoapConfig(coapConfig); + builder.setDtlsConfig(dtlsConfig); + builder.setRegistrationEngineFactory(engineFactory); + builder.setEndpointFactory(endpointFactory); + builder.setSharedExecutor(executor); + builder.setDecoder(new DefaultLwM2mNodeDecoder(true)); + builder.setEncoder(new DefaultLwM2mNodeEncoder(true)); + client = builder.build(); + + LwM2mClientObserver observer = new LwM2mClientObserver() { + @Override + public void onBootstrapStarted(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapStarted..."); + } + + @Override + public void onBootstrapSuccess(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapSuccess..."); + } + + @Override + public void onBootstrapFailure(ServerIdentity bsserver, BootstrapRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver -> onBootstrapFailure..."); + } + + @Override + public void onBootstrapTimeout(ServerIdentity bsserver, BootstrapRequest request) { + log.info("ClientObserver -> onBootstrapTimeout..."); + } + + @Override + public void onRegistrationStarted(ServerIdentity server, RegisterRequest request) { +// log.info("ClientObserver -> onRegistrationStarted... EndpointName [{}]", request.getEndpointName()); + } + + @Override + public void onRegistrationSuccess(ServerIdentity server, RegisterRequest request, String registrationID) { + log.info("ClientObserver -> onRegistrationSuccess... EndpointName [{}] [{}]", request.getEndpointName(), registrationID); + } + + @Override + public void onRegistrationFailure(ServerIdentity server, RegisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver -> onRegistrationFailure... ServerIdentity [{}]", server); + } + + @Override + public void onRegistrationTimeout(ServerIdentity server, RegisterRequest request) { + log.info("ClientObserver -> onRegistrationTimeout... RegisterRequest [{}]", request); + } + + @Override + public void onUpdateStarted(ServerIdentity server, UpdateRequest request) { +// log.info("ClientObserver -> onUpdateStarted... UpdateRequest [{}]", request); + } + + @Override + public void onUpdateSuccess(ServerIdentity server, UpdateRequest request) { +// log.info("ClientObserver -> onUpdateSuccess... UpdateRequest [{}]", request); + } + + @Override + public void onUpdateFailure(ServerIdentity server, UpdateRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + + } + + @Override + public void onUpdateTimeout(ServerIdentity server, UpdateRequest request) { + + } + + @Override + public void onDeregistrationStarted(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationStarted... DeregisterRequest [{}]", request.getRegistrationId()); + + } + + @Override + public void onDeregistrationSuccess(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationSuccess... DeregisterRequest [{}]", request.getRegistrationId()); + + } + + @Override + public void onDeregistrationFailure(ServerIdentity server, DeregisterRequest request, ResponseCode responseCode, String errorMessage, Exception cause) { + log.info("ClientObserver ->onDeregistrationFailure... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); + } + + @Override + public void onDeregistrationTimeout(ServerIdentity server, DeregisterRequest request) { + log.info("ClientObserver ->onDeregistrationTimeout... DeregisterRequest [{}] [{}]", request.getRegistrationId(), request.getRegistrationId()); + } + }; + this.client.addObserver(observer); + + client.start(); + } + + public void destroy() { + client.stop(false); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java new file mode 100644 index 0000000000..4512a94a27 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java @@ -0,0 +1,199 @@ +/** + * 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.extern.slf4j.Slf4j; +import org.eclipse.leshan.client.resource.BaseInstanceEnabler; +import org.eclipse.leshan.client.servers.ServerIdentity; +import org.eclipse.leshan.core.model.ObjectModel; +import org.eclipse.leshan.core.model.ResourceModel; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteResponse; + +import javax.security.auth.Destroyable; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.TimeZone; + +@Slf4j +public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyable { + + + private static final Random RANDOM = new Random(); + private static final List supportedResources = Arrays.asList(0, 1, 2, 3 +// , 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21 + ); + + @Override + public ReadResponse read(ServerIdentity identity, int resourceid) { + if (!identity.isSystem()) + log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); + switch (resourceid) { + case 0: + return ReadResponse.success(resourceid, getManufacturer()); + case 1: + return ReadResponse.success(resourceid, getModelNumber()); + case 2: + return ReadResponse.success(resourceid, getSerialNumber()); + case 3: + return ReadResponse.success(resourceid, getFirmwareVersion()); + case 9: + return ReadResponse.success(resourceid, getBatteryLevel()); + case 10: + return ReadResponse.success(resourceid, getMemoryFree()); + case 11: + Map errorCodes = new HashMap<>(); + errorCodes.put(0, getErrorCode()); + return ReadResponse.success(resourceid, errorCodes, ResourceModel.Type.INTEGER); + case 14: + return ReadResponse.success(resourceid, getUtcOffset()); + case 15: + return ReadResponse.success(resourceid, getTimezone()); + case 16: + return ReadResponse.success(resourceid, getSupportedBinding()); + case 17: + return ReadResponse.success(resourceid, getDeviceType()); + case 18: + return ReadResponse.success(resourceid, getHardwareVersion()); + case 19: + return ReadResponse.success(resourceid, getSoftwareVersion()); + case 20: + return ReadResponse.success(resourceid, getBatteryStatus()); + case 21: + return ReadResponse.success(resourceid, getMemoryTotal()); + default: + return super.read(identity, resourceid); + } + } + + @Override + public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) { + String withParams = null; + if (params != null && params.length() != 0) { + withParams = " with params " + params; + } + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceid, withParams != null ? withParams : ""); + return ExecuteResponse.success(); + } + + @Override + public WriteResponse write(ServerIdentity identity, int resourceid, LwM2mResource value) { + log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); + + switch (resourceid) { + case 13: + return WriteResponse.notFound(); + case 14: + setUtcOffset((String) value.getValue()); + fireResourcesChange(resourceid); + return WriteResponse.success(); + case 15: + setTimezone((String) value.getValue()); + fireResourcesChange(resourceid); + return WriteResponse.success(); + default: + return super.write(identity, resourceid, value); + } + } + + private String getManufacturer() { + return "Leshan Demo Device"; + } + + private String getModelNumber() { + return "Model 500"; + } + + private String getSerialNumber() { + return "LT-500-000-0001"; + } + + private String getFirmwareVersion() { + return "1.0.0"; + } + + private long getErrorCode() { + return 0; + } + + private int getBatteryLevel() { + return 42; + } + + private long getMemoryFree() { + return Runtime.getRuntime().freeMemory() / 1024; + } + + private String utcOffset = new SimpleDateFormat("X").format(Calendar.getInstance().getTime()); + + private String getUtcOffset() { + return utcOffset; + } + + private void setUtcOffset(String t) { + utcOffset = t; + } + + private String timeZone = TimeZone.getDefault().getID(); + + private String getTimezone() { + return timeZone; + } + + private void setTimezone(String t) { + timeZone = t; + } + + private String getSupportedBinding() { + return "U"; + } + + private String getDeviceType() { + return "Demo"; + } + + private String getHardwareVersion() { + return "1.0.1"; + } + + private String getSoftwareVersion() { + return "1.0.2"; + } + + private int getBatteryStatus() { + return RANDOM.nextInt(7); + } + + private long getMemoryTotal() { + return Runtime.getRuntime().totalMemory() / 1024; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + } +} diff --git a/application/src/test/resources/logback.xml b/application/src/test/resources/logback.xml index f991a40078..81d213b42d 100644 --- a/application/src/test/resources/logback.xml +++ b/application/src/test/resources/logback.xml @@ -9,13 +9,15 @@ - + + - + + diff --git a/application/src/test/resources/lwm2m/0.xml b/application/src/test/resources/lwm2m/0.xml new file mode 100644 index 0000000000..81e8523880 --- /dev/null +++ b/application/src/test/resources/lwm2m/0.xml @@ -0,0 +1,405 @@ + + + + + + + LWM2M Security + + 0 + urn:oma:lwm2m:oma:0:1.2 + 1.1 + 1.2 + Multiple + Mandatory + + + LWM2M Server URI + + Single + Mandatory + String + 0..255 + + + + + Bootstrap-Server + + Single + Mandatory + Boolean + + + + + + Security Mode + + Single + Mandatory + Integer + 0..4 + + + + + Public Key or Identity + + Single + Mandatory + Opaque + + + + + + Server Public Key + + Single + Mandatory + Opaque + + + + + + Secret Key + + Single + Mandatory + Opaque + + + + + + SMS Security Mode + + Single + Optional + Integer + 0..255 + + + + + SMS Binding Key Parameters + + Single + Optional + Opaque + 6 + + + + + SMS Binding Secret Key(s) + + Single + Optional + Opaque + 16,32,48 + + + + + LwM2M Server SMS Number + + Single + Optional + String + + + + + + Short Server ID + + Single + Optional + Integer + 1..65534 + + + + + Client Hold Off Time + + Single + Optional + Integer + + s + + + + Bootstrap-Server Account Timeout + + Single + Optional + Integer + + s + + + + Matching Type + + Single + Optional + Integer + 0..3 + + + + + SNI + + Single + Optional + String + + + + + + Certificate Usage + + Single + Optional + Integer + 0..3 + + + + + DTLS/TLS Ciphersuite + + Multiple + Optional + Integer + + + + + OSCORE Security Mode + + Single + Optional + Objlnk + + + + + + Groups To Use by Client + + Multiple + Optional + Integer + 0..65535 + + + + + Signature Algorithms Supported by Server + + Multiple + Optional + Integer + 0..65535 + + + + Signature Algorithms To Use by Client + + Multiple + Optional + Integer + 0..65535 + + + + + Signature Algorithm Certs Supported by Server + + Multiple + Optional + Integer + 0..65535 + + + + + TLS 1.3 Features To Use by Client + + Single + Optional + Integer + 0..65535 + + + + + TLS Extensions Supported by Server + + Single + Optional + Integer + 0..65535 + + + + + TLS Extensions To Use by Client + + Single + Optional + Integer + 0..65535 + + + + + Secondary LwM2M Server URI + + Multiple + Optional + String + 0..255 + + + + MQTT Server + + Single + Optional + Objlnk + + + + + LwM2M COSE Security + + Multiple + Optional + Objlnk + + + + + RDS Destination Port + + Single + Optional + Integer + 0..15 + + + + RDS Source Port + + Single + Optional + Integer + 0..15 + + + + RDS Application ID + + Single + Optional + String + + + + + + + + diff --git a/application/src/test/resources/lwm2m/1.xml b/application/src/test/resources/lwm2m/1.xml new file mode 100644 index 0000000000..f31e839c96 --- /dev/null +++ b/application/src/test/resources/lwm2m/1.xml @@ -0,0 +1,360 @@ + + + + + + + LwM2M Server + + 1 + urn:oma:lwm2m:oma:1:1.2 + 1.2 + 1.2 + Multiple + Mandatory + + + Short Server ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Lifetime + RW + Single + Mandatory + Integer + + s + + + + Default Minimum Period + RW + Single + Optional + Integer + + s + + + + Default Maximum Period + RW + Single + Optional + Integer + + s + + + + Disable + E + Single + Optional + + + + + + + Disable Timeout + RW + Single + Optional + Integer + + s + + + + Notification Storing When Disabled or Offline + RW + Single + Mandatory + Boolean + + + + + + Binding + RW + Single + Mandatory + String + + + + + + Registration Update Trigger + E + Single + Mandatory + + + + + + + Bootstrap-Request Trigger + E + Single + Optional + + + + + + + APN Link + RW + Single + Optional + Objlnk + + + + + + TLS-DTLS Alert Code + R + Single + Optional + Integer + 0..255 + + + + + Last Bootstrapped + R + Single + Optional + Time + + + + + + Registration Priority Order + R + Single + Optional + Integer + + + + + + Initial Registration Delay Timer + RW + Single + Optional + Integer + + s + + + + Registration Failure Block + R + Single + Optional + Boolean + + + + + + Bootstrap on Registration Failure + R + Single + Optional + Boolean + + + + + + Communication Retry Count + RW + Single + Optional + Integer + + + + + + Communication Retry Timer + RW + Single + Optional + Integer + + s + + + + Communication Sequence Delay Timer + RW + Single + Optional + Integer + + s + + + + Communication Sequence Retry Count + RW + Single + Optional + Integer + + + + + + Trigger + RW + Single + Optional + Boolean + + + + + + Preferred Transport + RW + Single + Optional + String + The possible values are those listed in the LwM2M Core Specification + + + + Mute Send + RW + Single + Optional + Boolean + + + + + + Alternate APN Links + RW + Multiple + Optional + Objlnk + + + + + + Supported Server Versions + RW + Multiple + Optional + String + + + + + + Default Notification Mode + RW + Single + Optional + Integer + 0..1 + + + + + Profile ID Hash Algorithm + RW + Single + Optional + Integer + 0..255 + + + + + + + diff --git a/application/src/test/resources/lwm2m/2.xml b/application/src/test/resources/lwm2m/2.xml new file mode 100644 index 0000000000..4ea5805b36 --- /dev/null +++ b/application/src/test/resources/lwm2m/2.xml @@ -0,0 +1,123 @@ + + + + + + + LwM2M Access Control + + 2 + urn:oma:lwm2m:oma:2:1.1 + 1.0 + 1.1 + Multiple + Optional + + + Object ID + R + Single + Mandatory + Integer + 1..65534 + + + + + Object Instance ID + R + Single + Mandatory + Integer + 0..65535 + + + + + ACL + RW + Multiple + Optional + Integer + 0..31 + + + + + Access Control Owner + RW + Single + Mandatory + Integer + 0..65535 + + + + + + + diff --git a/application/src/test/resources/lwm2m/3.xml b/application/src/test/resources/lwm2m/3.xml new file mode 100644 index 0000000000..724fc4cb33 --- /dev/null +++ b/application/src/test/resources/lwm2m/3.xml @@ -0,0 +1,331 @@ + + + + + + + Device + + 3 + urn:oma:lwm2m:oma:3:1.0 + 1.1 + 1.0 + Single + Mandatory + + + Manufacturer + R + Single + Optional + String + + + + + + Model Number + R + Single + Optional + String + + + + + + Serial Number + R + Single + Optional + String + + + + + + Firmware Version + R + Single + Optional + String + + + + + + Reboot + E + Single + Mandatory + + + + + + + Factory Reset + E + Single + Optional + + + + + + + Available Power Sources + R + Multiple + Optional + Integer + 0..7 + + + + + Power Source Voltage + R + Multiple + Optional + Integer + + + + + + Power Source Current + R + Multiple + Optional + Integer + + + + + + Battery Level + R + Single + Optional + Integer + 0..100 + /100 + + + + Memory Free + R + Single + Optional + Integer + + + + + + Error Code + R + Multiple + Mandatory + Integer + 0..32 + + + + + Reset Error Code + E + Single + Optional + + + + + + + Current Time + RW + Single + Optional + Time + + + + + + UTC Offset + RW + Single + Optional + String + + + + + + Timezone + RW + Single + Optional + String + + + + + + Supported Binding and Modes + R + Single + Mandatory + String + + + + + Device Type + R + Single + Optional + String + + + + + Hardware Version + R + Single + Optional + String + + + + + Software Version + R + Single + Optional + String + + + + + Battery Status + R + Single + Optional + Integer + 0..6 + + + + Memory Total + R + Single + Optional + Integer + + + + + ExtDevInfo + R + Multiple + Optional + Objlnk + + + + + + + diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java index 7c75589c4c..65be16bfd6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/HasKey.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.transport.lwm2m.secure.credentials; import org.eclipse.leshan.core.util.Hex; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java index 33a297c91a..65f027a849 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MClientCredentialsConfig.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.transport.lwm2m.secure.credentials; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java index ebb1fc138b..09c27f0e42 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/LwM2MCredentials.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.transport.lwm2m.secure.credentials; import lombok.Data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java index bcac7174aa..03933972c3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/NoSecClientCredentialsConfig.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.transport.lwm2m.secure.credentials; import org.eclipse.leshan.core.SecurityMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java index d79083647f..8de85ce72d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/PSKClientCredentialsConfig.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.transport.lwm2m.secure.credentials; import lombok.Data; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java index 280e8492f8..025c8b3b10 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/RPKClientCredentialsConfig.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.transport.lwm2m.secure.credentials; import org.eclipse.leshan.core.SecurityMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java index 0aff74d189..0a2df6852e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/credentials/X509ClientCredentialsConfig.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.transport.lwm2m.secure.credentials; import lombok.Data; 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..e93984e87c 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 @@ -646,8 +646,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { LwM2mClient lwM2MClient = clientContext.getOrRegister(registration); - if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config - .getModelProvider())) { + if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { if (FR_PATH_RESOURCE_VER_ID.equals(convertPathFromIdVerToObjectId(path)) && lwM2MClient.getFrUpdate().getCurrentFwVersion() != null && !lwM2MClient.getFrUpdate().getCurrentFwVersion().equals(lwM2MClient.getFrUpdate().getClientFwVersion()) From df8c9cecaecda12a08372a4de59ffe8e3a2a3507 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 6 May 2021 20:14:34 +0300 Subject: [PATCH 163/476] Minor improvements --- ui-ngx/src/app/core/http/alarm.service.ts | 2 +- .../modules/home/components/alarm/alarm-table-config.ts | 2 +- ui-ngx/src/app/shared/models/alarm.models.ts | 7 +------ ui-ngx/src/app/shared/models/device.models.ts | 1 + 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts index ab35c49b01..dc5771e31b 100644 --- a/ui-ngx/src/app/core/http/alarm.service.ts +++ b/ui-ngx/src/app/core/http/alarm.service.ts @@ -72,7 +72,7 @@ export class AlarmService { public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus, config?: RequestConfig): Observable { - let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`; + let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.id}`; if (alarmSearchStatus) { url += `?searchStatus=${alarmSearchStatus}`; } else if (alarmStatus) { diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts index 743b49c286..2bff69a53e 100644 --- a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts @@ -108,7 +108,7 @@ export class AlarmTableConfig extends EntityTableConfig } fetchAlarms(pageLink: TimePageLink): Observable> { - const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true, null); + const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true); return this.alarmService.getAlarms(query); } diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index a76bf1b425..c79935f23d 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/ui-ngx/src/app/shared/models/alarm.models.ts @@ -201,17 +201,15 @@ export class AlarmQuery { searchStatus: AlarmSearchStatus; status: AlarmStatus; fetchOriginator: boolean; - offset: string; constructor(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, - fetchOriginator: boolean, offset: string) { + fetchOriginator: boolean) { this.affectedEntityId = entityId; this.pageLink = pageLink; this.searchStatus = searchStatus; this.status = status; this.fetchOriginator = fetchOriginator; - this.offset = offset; } public toQuery(): string { @@ -225,9 +223,6 @@ export class AlarmQuery { if (typeof this.fetchOriginator !== 'undefined' && this.fetchOriginator !== null) { query += `&fetchOriginator=${this.fetchOriginator}`; } - if (isString(this.offset) && this.offset.length) { - query += `&offset=${this.offset}`; - } return query; } diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 81fdb7a8fd..5e5418fa4d 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -506,6 +506,7 @@ export interface DeviceProfile extends BaseData { export interface DeviceProfileInfo extends EntityInfoData { type: DeviceProfileType; transportType: DeviceTransportType; + image?: string; } export interface DefaultDeviceConfiguration { From 96878b6d1356c95a2c13ffbe32eb4fa08d30363a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 6 May 2021 20:36:02 +0300 Subject: [PATCH 164/476] Minor improvements --- .../java/org/thingsboard/server/controller/AlarmController.java | 1 - 1 file changed, 1 deletion(-) 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 6aec756f61..03883fc167 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -211,7 +211,6 @@ public class AlarmController extends BaseController { @RequestParam(required = false) Long endTime, @RequestParam(required = false) Boolean fetchOriginator ) throws ThingsboardException { - accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, Operation.READ); AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus); AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status); if (alarmSearchStatus != null && alarmStatus != null) { From 0f150337397472a1b0deb117a7eeb649c9dbcfff Mon Sep 17 00:00:00 2001 From: nickAS21 <44275303+nickAS21@users.noreply.github.com> Date: Fri, 7 May 2021 16:05:48 +0300 Subject: [PATCH 165/476] Lwm2m: fix bug add new instance (#4554) * Lwm2m: fix bug add new instance * Lwm2m: add KeyName default with instanceId --- ...-profile-transport-configuration.component.ts | 16 ++++++++++++---- .../lwm2m-observe-attr-telemetry.component.ts | 5 +++-- .../device/lwm2m/lwm2m-profile-config.models.ts | 2 +- 3 files changed, 16 insertions(+), 7 deletions(-) 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 60fb27a752..0a943ab5af 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 @@ -23,6 +23,7 @@ import { BINDING_MODE, BINDING_MODE_NAMES, getDefaultProfileConfig, + Instance, INSTANCES, KEY_NAME, Lwm2mProfileConfigModels, @@ -37,6 +38,7 @@ import { DeviceProfileService } from '@core/http/device-profile.service'; import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/utils'; import { JsonArray, JsonObject } from '@angular/compiler-cli/ngcc/src/packages/entry_point'; import { Direction } from '@shared/models/page/sort-order'; +import _ from 'lodash'; @Component({ selector: 'tb-profile-lwm2m-device-transport-configuration', @@ -230,24 +232,30 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro const isNotZeroInstanceId = (instance) => !instance.includes('/0/'); return attribute.some(isNotZeroInstanceId) || telemetry.some(isNotZeroInstanceId); } - private addInstances = (attribute: string[], telemetry: string[], clientObserveAttrTelemetry: ObjectLwM2M[]): void => { const instancesPath = attribute.concat(telemetry) .filter(instance => !instance.includes('/0/')) .map(instance => instance.slice(1, instance.lastIndexOf('/'))) .sort(this.sortPath); - new Set(instancesPath).forEach(path => { const pathParameter = Array.from(path.split('/'), String); const objectLwM2M = clientObserveAttrTelemetry.find(x => x.keyId === pathParameter[0]); if (objectLwM2M) { - const instance = deepClone(objectLwM2M.instances[0]); - instance.id = +pathParameter[1]; + const instance = this.updateInInstanceKeyName (objectLwM2M.instances[0], +pathParameter[1]); objectLwM2M.instances.push(instance); } }); } + private updateInInstanceKeyName = (instance: Instance, instanceId: number): Instance => { + const instanceUpdate = deepClone(instance); + instanceUpdate.id = instanceId; + instanceUpdate.resources.forEach(resource => { + resource.keyName = _.camelCase(resource.name + instanceUpdate.id); + }) + return instanceUpdate; + } + private updateObserveAttrTelemetryObjects = (parameters: string[], objectLwM2MS: ObjectLwM2M[], nameParameter: string): void => { parameters.forEach(parameter => { 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 3d6b93b84c..530679113c 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 @@ -45,6 +45,7 @@ import { Lwm2mObjectAddInstancesData, Lwm2mObjectAddInstancesDialogComponent } from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component'; +import _ from 'lodash'; @Component({ selector: 'tb-profile-lwm2m-observe-attr-telemetry', @@ -261,7 +262,6 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor r.attribute = false; r.telemetry = false; r.observe = false; - r.keyName = {}; r.attributeLwm2m = {}; }); const valueOld = this.instancesToSetId(instancesArray); @@ -280,7 +280,8 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor private addInstancesNew = (idsAdd: Set, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray, instanceNew: Instance): void => { idsAdd.forEach(x => { - this.pushInstance(instancesFormArray, x, instanceNew); + instanceNew.resources.forEach(resource => {resource.keyName = _.camelCase(resource.name + x);}); + this.pushInstance(instancesFormArray, x, deepClone(instanceNew as Instance)); }); (instancesFormArray.controls as FormGroup[]).sort((a, b) => a.value.id - b.value.id); } 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 f6e58df903..acea41394a 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 @@ -246,7 +246,7 @@ export interface ResourceLwM2M { observe: boolean; attribute: boolean; telemetry: boolean; - keyName: {}; + keyName: string; attributeLwm2m?: {}; } From 3094fb72400687f2d67ec0efeb58917814d12ad0 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 7 May 2021 16:05:54 +0300 Subject: [PATCH 166/476] Add customerId to alarm --- .../main/data/upgrade/3.2.2/schema_update.sql | 5 + .../update/DefaultDataUpdateService.java | 117 +++++++++++++-- .../install/update/PaginatedUpdater.java | 16 +++ .../server/dao/entity/EntityService.java | 2 + .../server/common/data/alarm/Alarm.java | 6 +- .../server/dao/alarm/BaseAlarmService.java | 2 + .../server/dao/entity/BaseEntityService.java | 46 ++++++ .../server/dao/model/ModelConstants.java | 1 + .../dao/model/sql/AbstractAlarmEntity.java | 12 ++ .../server/dao/model/sql/AlarmEntity.java | 5 + .../server/dao/rule/BaseRuleChainService.java | 136 +++++++----------- .../server/dao/rule/RuleChainDao.java | 9 ++ .../server/dao/sql/alarm/AlarmRepository.java | 16 +-- .../server/dao/sql/rule/JpaRuleChainDao.java | 6 + .../dao/sql/rule/RuleChainRepository.java | 3 + .../resources/sql/schema-entities-hsql.sql | 1 + .../main/resources/sql/schema-entities.sql | 1 + .../dashboard-page.component.html | 6 +- 18 files changed, 281 insertions(+), 109 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 4bb63f3b9f..4df24b4edf 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 @@ -90,6 +90,11 @@ ALTER TABLE device ADD COLUMN IF NOT EXISTS firmware_id uuid, ADD COLUMN IF NOT EXISTS software_id uuid; +ALTER TABLE alarm + ADD COLUMN IF NOT EXISTS customer_id uuid; + +DELETE FROM relation WHERE from_type = 'TENANT' AND relation_type_group = 'RULE_CHAIN'; + DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conname = 'fk_firmware_device_profile') THEN diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index 3f93aef41a..b91c46417c 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -27,6 +27,9 @@ import org.thingsboard.rule.engine.profile.TbDeviceProfileNode; import org.thingsboard.rule.engine.profile.TbDeviceProfileNodeConfiguration; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; @@ -34,9 +37,13 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; 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.data.page.TimePageLink; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.dao.alarm.AlarmDao; +import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; @@ -72,6 +79,15 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired private TimeseriesService tsService; + @Autowired + private AlarmService alarmService; + + @Autowired + private EntityService entityService; + + @Autowired + private AlarmDao alarmDao; + @Override public void updateData(String fromVersion) throws Exception { switch (fromVersion) { @@ -90,14 +106,25 @@ public class DefaultDataUpdateService implements DataUpdateService { case "3.2.2": log.info("Updating data from version 3.2.2 to 3.3.0 ..."); tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); + tenantsAlarmsCustomerUpdater.updateEntities(null); break; default: throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); } } - private PaginatedUpdater tenantsDefaultRuleChainUpdater = - new PaginatedUpdater() { + private final PaginatedUpdater tenantsDefaultRuleChainUpdater = + new PaginatedUpdater<>() { + + @Override + protected String getName() { + return "Tenants default rule chain updater"; + } + + @Override + protected boolean forceReportTotal() { + return true; + } @Override protected PageData findEntities(String region, PageLink pageLink) { @@ -117,8 +144,18 @@ public class DefaultDataUpdateService implements DataUpdateService { } }; - private PaginatedUpdater tenantsDefaultEdgeRuleChainUpdater = - new PaginatedUpdater() { + private final PaginatedUpdater tenantsDefaultEdgeRuleChainUpdater = + new PaginatedUpdater<>() { + + @Override + protected String getName() { + return "Tenants default edge rule chain updater"; + } + + @Override + protected boolean forceReportTotal() { + return true; + } @Override protected PageData findEntities(String region, PageLink pageLink) { @@ -138,8 +175,18 @@ public class DefaultDataUpdateService implements DataUpdateService { } }; - private PaginatedUpdater tenantsRootRuleChainUpdater = - new PaginatedUpdater() { + private final PaginatedUpdater tenantsRootRuleChainUpdater = + new PaginatedUpdater<>() { + + @Override + protected String getName() { + return "Tenants root rule chain updater"; + } + + @Override + protected boolean forceReportTotal() { + return true; + } @Override protected PageData findEntities(String region, PageLink pageLink) { @@ -192,8 +239,18 @@ public class DefaultDataUpdateService implements DataUpdateService { } }; - private PaginatedUpdater tenantsEntityViewsUpdater = - new PaginatedUpdater() { + private final PaginatedUpdater tenantsEntityViewsUpdater = + new PaginatedUpdater<>() { + + @Override + protected String getName() { + return "Tenants entity views updater"; + } + + @Override + protected boolean forceReportTotal() { + return true; + } @Override protected PageData findEntities(String region, PageLink pageLink) { @@ -261,4 +318,48 @@ public class DefaultDataUpdateService implements DataUpdateService { }, MoreExecutors.directExecutor()); } + private final PaginatedUpdater tenantsAlarmsCustomerUpdater = + new PaginatedUpdater<>() { + + @Override + protected String getName() { + return "Tenants alarms customer updater"; + } + + @Override + protected boolean forceReportTotal() { + return true; + } + + @Override + protected PageData findEntities(String region, PageLink pageLink) { + return tenantService.findTenants(pageLink); + } + + @Override + protected void updateEntity(Tenant tenant) { + updateTenantAlarmsCustomer(tenant.getId()); + } + }; + + private void updateTenantAlarmsCustomer(TenantId tenantId) { + AlarmQuery alarmQuery = new AlarmQuery(null, new TimePageLink(100), null, null, false); + PageData alarms = alarmDao.findAlarms(tenantId, alarmQuery); + boolean hasNext = true; + while (hasNext) { + for (Alarm alarm : alarms.getData()) { + if (alarm.getCustomerId() == null && alarm.getOriginator() != null) { + alarm.setCustomerId(entityService.fetchEntityCustomerId(tenantId, alarm.getOriginator())); + alarmDao.save(tenantId, alarm); + } + } + if (alarms.hasNext()) { + alarmQuery.setPageLink(alarmQuery.getPageLink().nextPageLink()); + alarms = alarmDao.findAlarms(tenantId, alarmQuery); + } else { + hasNext = false; + } + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java index e766bb0daa..4bfe744863 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java @@ -15,16 +15,20 @@ */ package org.thingsboard.server.service.install.update; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +@Slf4j public abstract class PaginatedUpdater> { private static final int DEFAULT_LIMIT = 100; + private int updated = 0; public void updateEntities(I id) { + updated = 0; PageLink pageLink = new PageLink(DEFAULT_LIMIT); boolean hasNext = true; while (hasNext) { @@ -32,13 +36,25 @@ public abstract class PaginatedUpdater DEFAULT_LIMIT || forceReportTotal()) { + log.info("{}: {} total entities updated.", getName(), updated); + } } } } + protected boolean forceReportTotal() { + return false; + } + + protected abstract String getName(); + protected abstract PageData findEntities(I id, PageLink pageLink); protected abstract void updateEntity(D entity); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java index 09c05e3c0c..e6a9538e54 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/entity/EntityService.java @@ -28,6 +28,8 @@ public interface EntityService { ListenableFuture fetchEntityNameAsync(TenantId tenantId, EntityId entityId); + CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId); + void deleteEntityRelations(TenantId tenantId, EntityId entityId); long countEntitiesByQuery(TenantId tenantId, CustomerId customerId, EntityCountQuery query); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java index 32d0a516eb..54f7ab72f5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java @@ -21,9 +21,11 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; 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; @@ -35,9 +37,10 @@ import java.util.List; @Data @Builder @AllArgsConstructor -public class Alarm extends BaseData implements HasName, HasTenantId { +public class Alarm extends BaseData implements HasName, HasTenantId, HasCustomerId { private TenantId tenantId; + private CustomerId customerId; private String type; private EntityId originator; private AlarmSeverity severity; @@ -62,6 +65,7 @@ public class Alarm extends BaseData implements HasName, HasTenantId { super(alarm.getId()); this.createdTime = alarm.getCreatedTime(); this.tenantId = alarm.getTenantId(); + this.customerId = alarm.getCustomerId(); this.type = alarm.getType(); this.originator = alarm.getOriginator(); this.severity = alarm.getSeverity(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 472cbb588f..b40993b76b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -110,6 +110,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ if (alarm.getEndTs() == 0L) { alarm.setEndTs(alarm.getStartTs()); } + alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator())); if (alarm.getId() == null) { Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); if (existing == null || existing.getStatus().isCleared()) { @@ -355,6 +356,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ existing.setStatus(alarm.getStatus()); existing.setSeverity(alarm.getSeverity()); existing.setDetails(alarm.getDetails()); + existing.setCustomerId(alarm.getCustomerId()); existing.setPropagate(existing.isPropagate() || alarm.isPropagate()); List existingPropagateRelationTypes = existing.getPropagateRelationTypes(); List newRelationTypes = alarm.getPropagateRelationTypes(); 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 7e0218740a..73cebc0797 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 @@ -22,6 +22,7 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; @@ -54,6 +55,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; +import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.service.Validator.validateId; /** @@ -175,6 +177,50 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe return entityName; } + @Override + public CustomerId fetchEntityCustomerId(TenantId tenantId, EntityId entityId) { + log.trace("Executing fetchEntityCustomerId [{}]", entityId); + HasCustomerId hasCustomerId = null; + switch (entityId.getEntityType()) { + case TENANT: + case RULE_CHAIN: + case RULE_NODE: + case DASHBOARD: + case WIDGETS_BUNDLE: + case WIDGET_TYPE: + case TENANT_PROFILE: + case DEVICE_PROFILE: + case API_USAGE_STATE: + case TB_RESOURCE: + case FIRMWARE: + break; + case CUSTOMER: + hasCustomerId = () -> new CustomerId(entityId.getId()); + break; + case USER: + hasCustomerId = userService.findUserById(tenantId, new UserId(entityId.getId())); + break; + case ASSET: + hasCustomerId = assetService.findAssetById(tenantId, new AssetId(entityId.getId())); + break; + case DEVICE: + hasCustomerId = deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())); + break; + case ALARM: + try { + hasCustomerId = alarmService.findAlarmByIdAsync(tenantId, new AlarmId(entityId.getId())).get(); + } catch (Exception e) {} + break; + case ENTITY_VIEW: + hasCustomerId = entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())); + break; + case EDGE: + hasCustomerId = edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())); + break; + } + return hasCustomerId != null ? hasCustomerId.getCustomerId() : new CustomerId(NULL_UUID); + } + private static void validateEntityCountQuery(EntityCountQuery query) { if (query == null) { throw new IncorrectParameterException("Query must be specified."); 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 5b4ea874ee..ee0d837ea0 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 @@ -258,6 +258,7 @@ public class ModelConstants { */ public static final String ALARM_COLUMN_FAMILY_NAME = "alarm"; public static final String ALARM_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; + public static final String ALARM_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY; public static final String ALARM_TYPE_PROPERTY = "type"; public static final String ALARM_DETAILS_PROPERTY = "details"; public static final String ALARM_ORIGINATOR_ID_PROPERTY = "originator_id"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java index 297fa39e62..8ade2b78d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java @@ -27,6 +27,7 @@ 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.AlarmId; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.model.BaseEntity; @@ -44,6 +45,7 @@ import java.util.UUID; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY; +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CUSTOMER_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; @@ -64,6 +66,9 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity @Column(name = ALARM_TENANT_ID_PROPERTY) private UUID tenantId; + @Column(name = ALARM_CUSTOMER_ID_PROPERTY) + private UUID customerId; + @Column(name = ALARM_ORIGINATOR_ID_PROPERTY) private UUID originatorId; @@ -115,6 +120,9 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity if (alarm.getTenantId() != null) { this.tenantId = alarm.getTenantId().getId(); } + if (alarm.getCustomerId() != null) { + this.customerId = alarm.getCustomerId().getId(); + } this.type = alarm.getType(); this.originatorId = alarm.getOriginator().getId(); this.originatorType = alarm.getOriginator().getEntityType(); @@ -138,6 +146,7 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity this.setId(alarmEntity.getId()); this.setCreatedTime(alarmEntity.getCreatedTime()); this.tenantId = alarmEntity.getTenantId(); + this.customerId = alarmEntity.getCustomerId(); this.type = alarmEntity.getType(); this.originatorId = alarmEntity.getOriginatorId(); this.originatorType = alarmEntity.getOriginatorType(); @@ -159,6 +168,9 @@ public abstract class AbstractAlarmEntity extends BaseSqlEntity if (tenantId != null) { alarm.setTenantId(new TenantId(tenantId)); } + if (customerId != null) { + alarm.setCustomerId(new CustomerId(customerId)); + } alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId)); alarm.setType(type); alarm.setSeverity(severity); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java index 73b181a170..5848ffa572 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java @@ -19,6 +19,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Entity; @@ -37,6 +38,10 @@ public final class AlarmEntity extends AbstractAlarmEntity { super(); } + public AlarmEntity(AlarmInfo alarmInfo) { + super(alarmInfo); + } + public AlarmEntity(Alarm alarm) { super(alarm); } 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 786d86d8dd..b61e18e08a 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 @@ -100,12 +100,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Transactional public RuleChain saveRuleChain(RuleChain ruleChain) { ruleChainValidator.validate(ruleChain, RuleChain::getTenantId); - RuleChain savedRuleChain = ruleChainDao.save(ruleChain.getTenantId(), ruleChain); - if (ruleChain.isRoot() && ruleChain.getId() == null) { - createRelation(ruleChain.getTenantId(), new EntityRelation(savedRuleChain.getTenantId(), savedRuleChain.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); - } - return savedRuleChain; + return ruleChainDao.save(ruleChain.getTenantId(), ruleChain); } @Override @@ -114,29 +109,20 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC RuleChain ruleChain = ruleChainDao.findById(tenantId, ruleChainId.getId()); if (!ruleChain.isRoot()) { RuleChain previousRootRuleChain = getRootTenantRuleChain(ruleChain.getTenantId()); - try { - if (previousRootRuleChain == null) { - setRootAndSave(tenantId, ruleChain); - return true; - } else if (!previousRootRuleChain.getId().equals(ruleChain.getId())) { - deleteRelation(tenantId, new EntityRelation(previousRootRuleChain.getTenantId(), previousRootRuleChain.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); - previousRootRuleChain.setRoot(false); - ruleChainDao.save(tenantId, previousRootRuleChain); - setRootAndSave(tenantId, ruleChain); - return true; - } - } catch (ExecutionException | InterruptedException e) { - log.warn("[{}] Failed to set root rule chain, ruleChainId: [{}]", ruleChainId); - throw new RuntimeException(e); + if (previousRootRuleChain == null) { + setRootAndSave(tenantId, ruleChain); + return true; + } else if (!previousRootRuleChain.getId().equals(ruleChain.getId())) { + previousRootRuleChain.setRoot(false); + ruleChainDao.save(tenantId, previousRootRuleChain); + setRootAndSave(tenantId, ruleChain); + return true; } } return false; } - private void setRootAndSave(TenantId tenantId, RuleChain ruleChain) throws ExecutionException, InterruptedException { - createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); + private void setRootAndSave(TenantId tenantId, RuleChain ruleChain) { ruleChain.setRoot(true); ruleChainDao.save(tenantId, ruleChain); } @@ -179,41 +165,45 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC toDelete.add(existingNode); } } - for (RuleNode node : toAddOrUpdate) { - node.setRuleChainId(ruleChain.getId()); - RuleNode savedNode = ruleNodeDao.save(tenantId, node); - 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); + if (nodes != null) { + for (RuleNode node : toAddOrUpdate) { + node.setRuleChainId(ruleChain.getId()); + RuleNode savedNode = ruleNodeDao.save(tenantId, node); + 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); + } } for (RuleNode node : toDelete) { deleteRuleNode(tenantId, node.getId()); } RuleNodeId firstRuleNodeId = null; - if (ruleChainMetaData.getFirstNodeIndex() != null) { - firstRuleNodeId = nodes.get(ruleChainMetaData.getFirstNodeIndex()).getId(); - } - if ((ruleChain.getFirstRuleNodeId() != null && !ruleChain.getFirstRuleNodeId().equals(firstRuleNodeId)) - || (ruleChain.getFirstRuleNodeId() == null && firstRuleNodeId != null)) { - ruleChain.setFirstRuleNodeId(firstRuleNodeId); - ruleChainDao.save(tenantId, ruleChain); - } - if (ruleChainMetaData.getConnections() != null) { - for (NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) { - EntityId from = nodes.get(nodeConnection.getFromIndex()).getId(); - EntityId to = nodes.get(nodeConnection.getToIndex()).getId(); - String type = nodeConnection.getType(); - createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); + if (nodes != null) { + if (ruleChainMetaData.getFirstNodeIndex() != null) { + firstRuleNodeId = nodes.get(ruleChainMetaData.getFirstNodeIndex()).getId(); } - } - if (ruleChainMetaData.getRuleChainConnections() != null) { - for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { - EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); - EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); - String type = nodeToRuleChainConnection.getType(); - createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); + if ((ruleChain.getFirstRuleNodeId() != null && !ruleChain.getFirstRuleNodeId().equals(firstRuleNodeId)) + || (ruleChain.getFirstRuleNodeId() == null && firstRuleNodeId != null)) { + ruleChain.setFirstRuleNodeId(firstRuleNodeId); + ruleChainDao.save(tenantId, ruleChain); + } + if (ruleChainMetaData.getConnections() != null) { + for (NodeConnectionInfo nodeConnection : ruleChainMetaData.getConnections()) { + EntityId from = nodes.get(nodeConnection.getFromIndex()).getId(); + EntityId to = nodes.get(nodeConnection.getToIndex()).getId(); + String type = nodeConnection.getType(); + createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE)); + } + } + if (ruleChainMetaData.getRuleChainConnections() != null) { + for (RuleChainConnectionInfo nodeToRuleChainConnection : ruleChainMetaData.getRuleChainConnections()) { + EntityId from = nodes.get(nodeToRuleChainConnection.getFromIndex()).getId(); + EntityId to = nodeToRuleChainConnection.getTargetRuleChainId(); + String type = nodeToRuleChainConnection.getType(); + createRelation(tenantId, new EntityRelation(from, to, type, RelationTypeGroup.RULE_NODE, nodeToRuleChainConnection.getAdditionalInfo())); + } } } @@ -307,24 +297,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override public RuleChain getRootTenantRuleChain(TenantId tenantId) { - return getRootRuleChainByType(tenantId, RuleChainType.CORE); - } - - private RuleChain getRootRuleChainByType(TenantId tenantId, RuleChainType type) { Validator.validateId(tenantId, "Incorrect tenant id for search request."); - List relations = relationService.findByFrom(tenantId, tenantId, RelationTypeGroup.RULE_CHAIN); - if (relations != null && !relations.isEmpty()) { - for (EntityRelation relation : relations) { - RuleChainId ruleChainId = new RuleChainId(relation.getTo().getId()); - RuleChain ruleChainById = findRuleChainById(tenantId, ruleChainId); - if (type.equals(ruleChainById.getType())) { - return ruleChainById; - } - } - return null; - } else { - return null; - } + return ruleChainDao.findRootRuleChainByTenantIdAndType(tenantId.getId(), RuleChainType.CORE); } @Override @@ -366,7 +340,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC boolean valid = true; EntityType toType = relation.getTo().getEntityType(); if (toType == EntityType.RULE_NODE || toType == EntityType.RULE_CHAIN) { - BaseData entity; + BaseData entity; if (relation.getTo().getEntityType() == EntityType.RULE_NODE) { entity = ruleNodeDao.findById(tenantId, relation.getTo().getId()); } else { @@ -501,9 +475,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ObjectNode objNode = (ObjectNode) node; objNode.put("id", tenantId.getId().toString()); } else { - Iterator childIter = node.iterator(); - while (childIter.hasNext()) { - searchTenantIdRecursive(tenantId, childIter.next()); + for (JsonNode jsonNode : node) { + searchTenantIdRecursive(tenantId, jsonNode); } } } @@ -608,7 +581,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC @Override public RuleChain getEdgeTemplateRootRuleChain(TenantId tenantId) { - return getRootRuleChainByType(tenantId, RuleChainType.EDGE); + Validator.validateId(tenantId, "Incorrect tenant id for search request."); + return ruleChainDao.findRootRuleChainByTenantIdAndType(tenantId.getId(), RuleChainType.EDGE); } @Override @@ -618,13 +592,9 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC if (previousEdgeTemplateRootRuleChain == null || !previousEdgeTemplateRootRuleChain.getId().equals(ruleChain.getId())) { try { if (previousEdgeTemplateRootRuleChain != null) { - deleteRelation(tenantId, new EntityRelation(previousEdgeTemplateRootRuleChain.getTenantId(), previousEdgeTemplateRootRuleChain.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); previousEdgeTemplateRootRuleChain.setRoot(false); ruleChainDao.save(tenantId, previousEdgeTemplateRootRuleChain); } - createRelation(tenantId, new EntityRelation(ruleChain.getTenantId(), ruleChain.getId(), - EntityRelation.CONTAINS_TYPE, RelationTypeGroup.RULE_CHAIN)); ruleChain.setRoot(true); ruleChainDao.save(tenantId, ruleChain); return true; @@ -699,8 +669,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ruleNodeDao.removeById(tenantId, entityId.getId()); } - private DataValidator ruleChainValidator = - new DataValidator() { + private final DataValidator ruleChainValidator = + new DataValidator<>() { @Override protected void validateCreate(TenantId tenantId, RuleChain data) { DefaultTenantProfileConfiguration profileConfiguration = @@ -739,8 +709,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } }; - private PaginatedRemover tenantRuleChainsRemover = - new PaginatedRemover() { + private final PaginatedRemover tenantRuleChainsRemover = + new PaginatedRemover<>() { @Override protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java index 4baa3bd576..2aaaa934d7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/RuleChainDao.java @@ -50,6 +50,15 @@ public interface RuleChainDao extends Dao, TenantEntityDao { */ PageData findRuleChainsByTenantIdAndType(UUID tenantId, RuleChainType type, PageLink pageLink); + /** + * Find root rule chain by tenantId and type + * + * @param tenantId the tenantId + * @param type the type + * @return the rule chain object + */ + RuleChain findRootRuleChainByTenantIdAndType(UUID tenantId, RuleChainType type); + /** * Find rule chains by tenantId, edgeId and page link. * 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 028eda1583..b4c0ac09c6 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 @@ -120,13 +120,7 @@ public interface AlarmRepository extends CrudRepository { Pageable pageable); @Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " + - "WHERE a.tenantId = :tenantId " + - "AND (" + - "a.originatorId IN (SELECT d.id from DeviceEntity d WHERE d.customerId = :customerId) " + - "OR a.originatorId IN (SELECT asset.id from AssetEntity asset WHERE asset.customerId = :customerId) " + - "OR a.originatorId IN (SELECT u.id from UserEntity u WHERE u.customerId = :customerId) " + - "OR a.originatorId = :customerId" + - ") " + + "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "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)) " + @@ -137,13 +131,7 @@ public interface AlarmRepository extends CrudRepository { countQuery = "" + "SELECT count(a) " + "FROM AlarmEntity a " + - "WHERE a.tenantId = :tenantId " + - "AND (" + - "a.originatorId IN (SELECT d.id from DeviceEntity d WHERE d.customerId = :customerId) " + - "OR a.originatorId IN (SELECT asset.id from AssetEntity asset WHERE asset.customerId = :customerId) " + - "OR a.originatorId IN (SELECT u.id from UserEntity u WHERE u.customerId = :customerId) " + - "OR a.originatorId = :customerId" + - ") " + + "WHERE a.tenantId = :tenantId AND a.customerId = :customerId " + "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)) " + diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java index 760d9e7aeb..0dbe22571e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/JpaRuleChainDao.java @@ -82,6 +82,12 @@ public class JpaRuleChainDao extends JpaAbstractSearchTextDao findRuleChainsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink) { log.debug("Try to find rule chains by tenantId [{}], edgeId [{}] and pageLink [{}]", tenantId, edgeId, pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java index 3c3c5ff3b4..1251234fa1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rule/RuleChainRepository.java @@ -50,5 +50,8 @@ public interface RuleChainRepository extends PagingAndSortingRepository
= []; + private cellStyleCache: Array = []; + private rowStyleCache: Array = []; + private settings: AlarmsTableWidgetSettings; private widgetConfig: WidgetConfig; private subscription: IWidgetSubscription; @@ -261,6 +273,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, public onDataUpdated() { this.updateTitle(true); this.alarmsDatasource.updateAlarms(); + this.clearCache(); } public pageLinkSortDirection(): SortDirection { @@ -484,6 +497,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, if (this.actionCellDescriptors.length) { this.displayedColumns.push('actions'); } + this.clearCache(); } } as DisplayColumnsPanelData }, @@ -609,91 +623,100 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, return widthStyle(columnWidth); } - public rowStyle(alarm: AlarmDataInfo): any { - let style: any = {}; - if (alarm) { - if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { + public rowStyle(alarm: AlarmDataInfo, row: number): any { + let res = this.rowStyleCache[row]; + if (!res) { + res = {}; + if (alarm && this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { try { - style = this.rowStylesInfo.rowStyleFunction(alarm, this.ctx); - if (!isObject(style)) { - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); + res = this.rowStylesInfo.rowStyleFunction(alarm, this.ctx); + if (!isObject(res)) { + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`); } - if (Array.isArray(style)) { + if (Array.isArray(res)) { throw new TypeError(`Array instead of style object`); } } catch (e) { - style = {}; + res = {}; console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` + `returns '${e}'. Please check your row style function.`); } - } else { - style = {}; } + this.rowStyleCache[row] = res; } - return style; + return res; } - public cellStyle(alarm: AlarmDataInfo, key: EntityColumn): any { - let style: any = {}; - if (alarm && key) { - const styleInfo = this.stylesInfo[key.def]; - const value = getAlarmValue(alarm, key); - if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { - try { - style = styleInfo.cellStyleFunction(value, alarm, this.ctx); - if (!isObject(style)) { - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); - } - if (Array.isArray(style)) { - throw new TypeError(`Array instead of style object`); + public cellStyle(alarm: AlarmDataInfo, key: EntityColumn, row: number): any { + const col = this.columns.indexOf(key); + const index = row * this.columns.length + col; + let res = this.cellStyleCache[index]; + if (!res) { + res = {}; + if (alarm && key) { + const styleInfo = this.stylesInfo[key.def]; + const value = getAlarmValue(alarm, key); + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { + try { + res = styleInfo.cellStyleFunction(value, alarm, this.ctx); + if (!isObject(res)) { + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`); + } + if (Array.isArray(res)) { + throw new TypeError(`Array instead of style object`); + } + } catch (e) { + res = {}; + console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` + + `returns '${e}'. Please check your cell style function.`); } - } catch (e) { - style = {}; - console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` + - `returns '${e}'. Please check your cell style function.`); + } else { + res = this.defaultStyle(key, value); } - } else { - style = this.defaultStyle(key, value); } + this.cellStyleCache[index] = res; } - if (!style.width) { + if (!res.width) { const columnWidth = this.columnWidth[key.def]; - style = {...style, ...widthStyle(columnWidth)}; + res = Object.assign(res, widthStyle(columnWidth)); } - return style; + return res; } - public cellContent(alarm: AlarmDataInfo, key: EntityColumn): SafeHtml { - if (alarm && key) { - const contentInfo = this.contentsInfo[key.def]; - const value = getAlarmValue(alarm, key); - let content = ''; - if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { - try { - content = contentInfo.cellContentFunction(value, alarm, this.ctx); - } catch (e) { - content = '' + value; + public cellContent(alarm: AlarmDataInfo, key: EntityColumn, row: number): SafeHtml { + const col = this.columns.indexOf(key); + const index = row * this.columns.length + col; + let res = this.cellContentCache[index]; + if (isUndefined(res)) { + res = ''; + if (alarm && key) { + const contentInfo = this.contentsInfo[key.def]; + const value = getAlarmValue(alarm, key); + let content = ''; + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { + try { + content = contentInfo.cellContentFunction(value, alarm, this.ctx); + } catch (e) { + content = '' + value; + } + } else { + content = this.defaultContent(key, contentInfo, value); } - } else { - content = this.defaultContent(key, contentInfo, value); - } - - if (!isDefined(content)) { - return ''; - } else { - content = this.utils.customTranslation(content, content); - switch (typeof content) { - case 'string': - return this.domSanitizer.bypassSecurityTrustHtml(content); - default: - return content; + if (isDefined(content)) { + content = this.utils.customTranslation(content, content); + switch (typeof content) { + case 'string': + res = this.domSanitizer.bypassSecurityTrustHtml(content); + break; + default: + res = content; + } } } - - } else { - return ''; + this.cellContentCache[index] = res; } + return res; } public onRowClick($event: Event, alarm: AlarmDataInfo) { @@ -936,6 +959,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, isSorting(column: EntityColumn): boolean { return column.type === DataKeyType.alarm && column.name.startsWith('details.'); } + + private clearCache() { + this.cellContentCache.length = 0; + this.cellStyleCache.length = 0; + this.rowStyleCache.length = 0; + } } class AlarmsDatasource implements DataSource { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html index 680ed3ad97..ca93253ddc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.html @@ -42,9 +42,9 @@ matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLinkSortDirection()" matSortDisableClear> {{ column.title }} - + @@ -84,8 +84,8 @@ = []; + private cellStyleCache: Array = []; + private rowStyleCache: Array = []; + private settings: EntitiesTableWidgetSettings; private widgetConfig: WidgetConfig; private subscription: IWidgetSubscription; @@ -222,6 +235,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni public onDataUpdated() { this.updateTitle(true); this.entityDatasource.dataUpdated(); + this.clearCache(); } public pageLinkSortDirection(): SortDirection { @@ -460,6 +474,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni if (this.actionCellDescriptors.length) { this.displayedColumns.push('actions'); } + this.clearCache(); } } as DisplayColumnsPanelData }, @@ -535,91 +550,98 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni return widthStyle(columnWidth); } - public rowStyle(entity: EntityData): any { - let style: any = {}; - if (entity) { - if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { + public rowStyle(entity: EntityData, row: number): any { + let res = this.rowStyleCache[row]; + if (!res) { + res = {}; + if (entity && this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { try { - style = this.rowStylesInfo.rowStyleFunction(entity, this.ctx); - if (!isObject(style)) { - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); + res = this.rowStylesInfo.rowStyleFunction(entity, this.ctx); + if (!isObject(res)) { + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`); } - if (Array.isArray(style)) { + if (Array.isArray(res)) { throw new TypeError(`Array instead of style object`); } } catch (e) { - style = {}; + res = {}; console.warn(`Row style function in widget '${this.ctx.widgetTitle}' ` + `returns '${e}'. Please check your row style function.`); } - } else { - style = {}; } + this.rowStyleCache[row] = res; } - return style; - } - - public cellStyle(entity: EntityData, key: EntityColumn): any { - let style: any = {}; - if (entity && key) { - const styleInfo = this.stylesInfo[key.def]; - const value = getEntityValue(entity, key); - if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { - try { - style = styleInfo.cellStyleFunction(value, entity, this.ctx); - if (!isObject(style)) { - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); - } - if (Array.isArray(style)) { - throw new TypeError(`Array instead of style object`); + return res; + } + + public cellStyle(entity: EntityData, key: EntityColumn, row: number): any { + const col = this.columns.indexOf(key); + const index = row * this.columns.length + col; + let res = this.cellStyleCache[index]; + if (!res) { + res = {}; + if (entity && key) { + const styleInfo = this.stylesInfo[key.def]; + const value = getEntityValue(entity, key); + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { + try { + res = styleInfo.cellStyleFunction(value, entity, this.ctx); + if (!isObject(res)) { + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`); + } + if (Array.isArray(res)) { + throw new TypeError(`Array instead of style object`); + } + } catch (e) { + res = {}; + console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` + + `returns '${e}'. Please check your cell style function.`); } - } catch (e) { - style = {}; - console.warn(`Cell style function for data key '${key.label}' in widget '${this.ctx.widgetTitle}' ` + - `returns '${e}'. Please check your cell style function.`); } - } else { - style = {}; + this.cellStyleCache[index] = res; } } - if (!style.width) { + if (!res.width) { const columnWidth = this.columnWidth[key.def]; - style = {...style, ...widthStyle(columnWidth)}; + res = Object.assign(res, widthStyle(columnWidth)); } - return style; - } - - public cellContent(entity: EntityData, key: EntityColumn): SafeHtml { - if (entity && key) { - const contentInfo = this.contentsInfo[key.def]; - const value = getEntityValue(entity, key); - let content: string; - if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { - try { - content = contentInfo.cellContentFunction(value, entity, this.ctx); - } catch (e) { - content = '' + value; + return res; + } + + public cellContent(entity: EntityData, key: EntityColumn, row: number): SafeHtml { + const col = this.columns.indexOf(key); + const index = row * this.columns.length + col; + let res = this.cellContentCache[index]; + if (isUndefined(res)) { + res = ''; + if (entity && key) { + const contentInfo = this.contentsInfo[key.def]; + const value = getEntityValue(entity, key); + let content: string; + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { + try { + content = contentInfo.cellContentFunction(value, entity, this.ctx); + } catch (e) { + content = '' + value; + } + } else { + content = this.defaultContent(key, contentInfo, value); } - } else { - content = this.defaultContent(key, contentInfo, value); - } - - if (!isDefined(content)) { - return ''; - } else { - content = this.utils.customTranslation(content, content); - switch (typeof content) { - case 'string': - return this.domSanitizer.bypassSecurityTrustHtml(content); - default: - return content; + if (isDefined(content)) { + content = this.utils.customTranslation(content, content); + switch (typeof content) { + case 'string': + res = this.domSanitizer.bypassSecurityTrustHtml(content); + break; + default: + res = content; + } } } - - } else { - return ''; + this.cellContentCache[index] = res; } + return res; } private defaultContent(key: EntityColumn, contentInfo: CellContentInfo, value: any): any { @@ -672,6 +694,12 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } this.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {entity}, entityLabel); } + + private clearCache() { + this.cellContentCache.length = 0; + this.cellStyleCache.length = 0; + this.rowStyleCache.length = 0; + } } class EntityDatasource implements DataSource { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html index a92b4e16bb..4524082e5b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.html @@ -39,23 +39,23 @@ - +
Timestamp - + {{ h.dataKey.label }} - + @@ -93,8 +93,8 @@ -
= []; + private cellStyleCache: Array = []; + private rowStyleCache: Array = []; + private settings: TimeseriesTableWidgetSettings; private widgetConfig: WidgetConfig; private data: Array; @@ -194,6 +198,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI public onDataUpdated() { this.updateCurrentSourceData(); + this.clearCache(); } private initialize() { @@ -337,6 +342,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI onSourceIndexChanged() { this.updateCurrentSourceData(); this.updateActiveEntityInfo(); + this.clearCache(); } private enterFilterMode() { @@ -378,6 +384,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI source.pageLink.sortOrder.property = sort.active; source.pageLink.sortOrder.direction = Direction[sort.direction.toUpperCase()]; source.timeseriesDatasource.loadRows(); + this.clearCache(); this.ctx.detectChanges(); } @@ -397,94 +404,109 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI return source.datasource.entityId; } - public rowStyle(source: TimeseriesTableSource, row: TimeseriesRow): any { - let style: any = {}; - if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { - try { - const rowData = source.rowDataTemplate; - rowData.Timestamp = row[0]; - source.header.forEach((headerInfo) => { - rowData[headerInfo.dataKey.name] = row[headerInfo.index]; - }); - style = this.rowStylesInfo.rowStyleFunction(rowData, this.ctx); - if (!isObject(style)) { - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); - } - if (Array.isArray(style)) { - throw new TypeError(`Array instead of style object`); - } - } catch (e) { - style = {}; - console.warn(`Row style function in widget ` + - `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your row style function.`); - } - } - return style; - } - - public cellStyle(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any): any { - let style: any = {}; - if (index > 0) { - const styleInfo = source.stylesInfo[index - 1]; - if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { + public rowStyle(source: TimeseriesTableSource, row: TimeseriesRow, index: number): any { + let res = this.rowStyleCache[index]; + if (!res) { + res = {}; + if (this.rowStylesInfo.useRowStyleFunction && this.rowStylesInfo.rowStyleFunction) { try { const rowData = source.rowDataTemplate; rowData.Timestamp = row[0]; source.header.forEach((headerInfo) => { rowData[headerInfo.dataKey.name] = row[headerInfo.index]; }); - style = styleInfo.cellStyleFunction(value, rowData, this.ctx); - if (!isObject(style)) { - throw new TypeError(`${style === null ? 'null' : typeof style} instead of style object`); + res = this.rowStylesInfo.rowStyleFunction(rowData, this.ctx); + if (!isObject(res)) { + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`); } - if (Array.isArray(style)) { + if (Array.isArray(res)) { throw new TypeError(`Array instead of style object`); } } catch (e) { - style = {}; - console.warn(`Cell style function for data key '${source.header[index - 1].dataKey.label}' in widget ` + - `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your cell style function.`); + res = {}; + console.warn(`Row style function in widget ` + + `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your row style function.`); } } + this.rowStyleCache[index] = res; } - return style; - } - - public cellContent(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any): SafeHtml { - if (index === 0) { - return row.formattedTs; - } else { - let content; - const contentInfo = source.contentsInfo[index - 1]; - if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { - try { - const rowData = source.rowDataTemplate; - rowData.Timestamp = row[0]; - source.header.forEach((headerInfo) => { - rowData[headerInfo.dataKey.name] = row[headerInfo.index]; - }); - content = contentInfo.cellContentFunction(value, rowData, this.ctx); - } catch (e) { - content = '' + value; + return res; + } + + public cellStyle(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any, rowIndex: number): any { + const cacheIndex = rowIndex * (source.header.length + 1) + index; + let res = this.cellStyleCache[cacheIndex]; + if (!res) { + res = {}; + if (index > 0) { + const styleInfo = source.stylesInfo[index - 1]; + if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) { + try { + const rowData = source.rowDataTemplate; + rowData.Timestamp = row[0]; + source.header.forEach((headerInfo) => { + rowData[headerInfo.dataKey.name] = row[headerInfo.index]; + }); + res = styleInfo.cellStyleFunction(value, rowData, this.ctx); + if (!isObject(res)) { + throw new TypeError(`${res === null ? 'null' : typeof res} instead of style object`); + } + if (Array.isArray(res)) { + throw new TypeError(`Array instead of style object`); + } + } catch (e) { + res = {}; + console.warn(`Cell style function for data key '${source.header[index - 1].dataKey.label}' in widget ` + + `'${this.ctx.widgetConfig.title}' returns '${e}'. Please check your cell style function.`); + } } - } else { - const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals; - const units = contentInfo.units || this.ctx.widgetConfig.units; - content = this.ctx.utils.formatValue(value, decimals, units, true); } + this.cellStyleCache[cacheIndex] = res; + } + return res; + } - if (!isDefined(content)) { - return ''; + public cellContent(source: TimeseriesTableSource, index: number, row: TimeseriesRow, value: any, rowIndex: number): SafeHtml { + const cacheIndex = rowIndex * (source.header.length + 1) + index ; + let res = this.cellContentCache[cacheIndex]; + if (isUndefined(res)) { + res = ''; + if (index === 0) { + res = row.formattedTs; } else { - content = this.utils.customTranslation(content, content); - switch (typeof content) { - case 'string': - return this.domSanitizer.bypassSecurityTrustHtml(content); - default: - return content; + let content; + const contentInfo = source.contentsInfo[index - 1]; + if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) { + try { + const rowData = source.rowDataTemplate; + rowData.Timestamp = row[0]; + source.header.forEach((headerInfo) => { + rowData[headerInfo.dataKey.name] = row[headerInfo.index]; + }); + content = contentInfo.cellContentFunction(value, rowData, this.ctx); + } catch (e) { + content = '' + value; + } + } else { + const decimals = (contentInfo.decimals || contentInfo.decimals === 0) ? contentInfo.decimals : this.ctx.widgetConfig.decimals; + const units = contentInfo.units || this.ctx.widgetConfig.units; + content = this.ctx.utils.formatValue(value, decimals, units, true); + } + + if (isDefined(content)) { + content = this.utils.customTranslation(content, content); + switch (typeof content) { + case 'string': + res = this.domSanitizer.bypassSecurityTrustHtml(content); + break; + default: + res = content; + } } } + this.cellContentCache[cacheIndex] = res; } + return res; } public onRowClick($event: Event, row: TimeseriesRow) { @@ -530,6 +552,13 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI private loadCurrentSourceRow() { this.sources[this.sourceIndex].timeseriesDatasource.loadRows(); + this.clearCache(); + } + + private clearCache() { + this.cellContentCache.length = 0; + this.cellStyleCache.length = 0; + this.rowStyleCache.length = 0; } } From f862034f79172e4e32d23c17e0b03c0bf098831f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 14 Jun 2021 18:40:14 +0300 Subject: [PATCH 371/476] Implementation of LwM2M RPC commands --- .../lwm2m/server/LwM2mOperationType.java | 53 ++--- .../lwm2m/server/client/LwM2mClient.java | 25 +-- .../lwm2m/server/client/LwM2mFwSwUpdate.java | 6 +- .../DefaultLwM2mDownlinkMsgHandler.java | 83 ++++---- .../downlink/LwM2mDownlinkMsgHandler.java | 4 +- ...java => TbLwM2MWriteResponseCallback.java} | 4 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 200 ++++++++++++++---- .../server/rpc/LwM2MRpcResponseBody.java | 32 +++ .../server/rpc/RpcCancelObserveCallback.java | 41 ++++ .../lwm2m/server/rpc/RpcDiscoverCallback.java | 43 ++++ .../rpc/RpcDownlinkRequestCallbackProxy.java | 86 ++++++++ .../server/rpc/RpcEmptyResponseCallback.java | 38 ++++ .../lwm2m/server/rpc/RpcLinkSetCallback.java | 41 ++++ .../server/rpc/RpcLwM2MDownlinkCallback.java | 50 +++++ .../server/rpc/RpcReadResponseCallback.java | 50 +++++ .../server/rpc/RpcWriteAttributesRequest.java | 28 +++ .../server/rpc/RpcWriteReplaceRequest.java | 27 +++ .../server/rpc/RpcWriteUpdateRequest.java | 29 +++ .../uplink/DefaultLwM2MUplinkMsgHandler.java | 29 +-- 19 files changed, 719 insertions(+), 150 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/{TbLwM2MWriteReplaceCallback.java => TbLwM2MWriteResponseCallback.java} (84%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcResponseBody.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcEmptyResponseCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLwM2MDownlinkCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java index 694d7b96c7..f9b3f93854 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java @@ -15,56 +15,57 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import lombok.Getter; + /** * Define the behavior of a write request. */ public enum LwM2mOperationType { - /** - * GET - */ - READ(0, "Read"), - DISCOVER(1, "Discover"), - DISCOVER_ALL(2, "DiscoverAll"), - OBSERVE_READ_ALL(3, "ObserveReadAll"), - /** - * POST - */ - OBSERVE(4, "Observe"), - OBSERVE_CANCEL(5, "ObserveCancel"), - OBSERVE_CANCEL_ALL(6, "ObserveCancelAll"), - EXECUTE(7, "Execute"), + + READ(0, "Read", true), + DISCOVER(1, "Discover", true), + DISCOVER_ALL(2, "DiscoverAll", false), + OBSERVE_READ_ALL(3, "ObserveReadAll", false), + + OBSERVE(4, "Observe", true), + OBSERVE_CANCEL(5, "ObserveCancel", true), + OBSERVE_CANCEL_ALL(6, "ObserveCancelAll", false), + EXECUTE(7, "Execute", true), /** * Replaces the Object Instance or the Resource(s) with the new value provided in the “Write” operation. (see * section 5.3.3 of the LW M2M spec). * if all resources are to be replaced */ - WRITE_REPLACE(8, "WriteReplace"), - /* - PUT - */ + WRITE_REPLACE(8, "WriteReplace", true), + /** * Adds or updates Resources provided in the new value and leaves other existing Resources unchanged. (see section * 5.3.3 of the LW M2M spec). * if this is a partial update request */ - WRITE_UPDATE(9, "WriteUpdate"), - WRITE_ATTRIBUTES(10, "WriteAttributes"), - DELETE(11, "Delete"), + WRITE_UPDATE(9, "WriteUpdate", true), + WRITE_ATTRIBUTES(10, "WriteAttributes", true), + DELETE(11, "Delete", true), // only for RPC - FW_UPDATE(12, "FirmwareUpdate"); -// FW_READ_INFO(12, "FirmwareReadInfo"), + FW_UPDATE(12, "FirmwareUpdate", false); +// FW_READ_INFO(12, "FirmwareReadInfo"), // SW_READ_INFO(15, "SoftwareReadInfo"), // SW_UPDATE(16, "SoftwareUpdate"), // SW_UNINSTALL(18, "SoftwareUninstall"); - public int code; - public String type; + @Getter + private final int code; + @Getter + private final String type; + @Getter + private final boolean hasObjectId; - LwM2mOperationType(int code, String type) { + LwM2mOperationType(int code, String type, boolean hasObjectId) { this.code = code; this.type = type; + this.hasObjectId = hasObjectId; } public static LwM2mOperationType fromType(String type) { 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 1ce9358078..77454d8b83 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,6 +26,7 @@ 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.node.codec.LwM2mValueConverter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; @@ -40,7 +41,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.Collection; import java.util.List; @@ -257,7 +257,7 @@ public class LwM2mClient implements Cloneable { .getObjectModel(pathIds.getObjectId()) : null; } - public String objectToString(LwM2mObject lwM2mObject, LwM2mValueConverterImpl converter, String pathIdVer) { + public String objectToString(LwM2mObject lwM2mObject, LwM2mValueConverter converter, String pathIdVer) { StringBuilder builder = new StringBuilder(); builder.append("LwM2mObject [id=").append(lwM2mObject.getId()).append(", instances={"); lwM2mObject.getInstances().forEach((instId, inst) -> { @@ -271,7 +271,7 @@ public class LwM2mClient implements Cloneable { return builder.toString(); } - public String instanceToString(LwM2mObjectInstance objectInstance, LwM2mValueConverterImpl converter, String pathIdVer) { + public String instanceToString(LwM2mObjectInstance objectInstance, LwM2mValueConverter converter, String pathIdVer) { StringBuilder builder = new StringBuilder(); builder.append("LwM2mObjectInstance [id=").append(objectInstance.getId()).append(", resources={"); objectInstance.getResources().forEach((resId, res) -> { @@ -285,19 +285,12 @@ public class LwM2mClient implements Cloneable { return builder.toString(); } - public String resourceToString(LwM2mResource lwM2mResource, LwM2mValueConverterImpl converter, String pathIdVer) { - if (!OPAQUE.equals(lwM2mResource.getType())) { - return lwM2mResource.isMultiInstances() ? ((LwM2mMultipleResource) lwM2mResource).toString() : - ((LwM2mSingleResource) lwM2mResource).toString(); - } else { - return String.format("LwM2mSingleResource [id=%s, value=%s, type=%s]", lwM2mResource.getId(), - converter.convertValue(lwM2mResource.getValue(), - OPAQUE, STRING, new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))), lwM2mResource.getType().name()); - } + public String resourceToString(LwM2mResource lwM2mResource, LwM2mValueConverter converter, String pathIdVer) { + return lwM2mResource.getValue().toString(); } public Collection getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, - LwM2mValueConverterImpl converter) { + LwM2mValueConverter converter) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) @@ -313,7 +306,7 @@ public class LwM2mClient implements Cloneable { } public Collection getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, - LwM2mValueConverterImpl converter) { + LwM2mValueConverter converter) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) @@ -395,7 +388,7 @@ public class LwM2mClient implements Cloneable { } } - public LwM2mFwSwUpdate getFwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClientContext clientContext) { + public LwM2mFwSwUpdate getFwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClientContext clientContext) { if (this.fwUpdate == null) { var profile = clientContext.getProfile(this.getProfileId()); this.fwUpdate = new LwM2mFwSwUpdate(handler, this, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy()); @@ -403,7 +396,7 @@ public class LwM2mClient implements Cloneable { return this.fwUpdate; } - public LwM2mFwSwUpdate getSwUpdate (LwM2mUplinkMsgHandler handler,LwM2mClientContext clientContext) { + public LwM2mFwSwUpdate getSwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClientContext clientContext) { if (this.swUpdate == null) { var profile = clientContext.getProfile(this.getProfileId()); this.swUpdate = new LwM2mFwSwUpdate(handler, this, OtaPackageType.SOFTWARE, profile.getClientLwM2mSettings().getSwUpdateStrategy()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java index d4efda61ab..6896c3d8de 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java @@ -33,7 +33,7 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import java.util.ArrayList; @@ -201,14 +201,14 @@ public class LwM2mFwSwUpdate { byte[] firmwareChunk = handler.otaPackageDataCache.get(this.currentId.toString(), chunkSize, chunk); TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(firmwareChunk).timeout(handler.config.getTimeout()).build(); - request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteReplaceCallback(handler, lwM2MClient, targetIdVer)); + request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteResponseCallback(handler, lwM2MClient, targetIdVer)); } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) { String apiFont = "coap://176.36.143.9:5685"; String uri = apiFont + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + this.currentId.toString(); log.warn("89) coapUri: [{}]", uri); //TODO: user this.rpcRequest??? TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(uri).timeout(handler.config.getTimeout()).build(); - request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteReplaceCallback(handler, lwM2MClient, targetIdVer)); + request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteResponseCallback(handler, lwM2MClient, targetIdVer)); } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) { } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 141fa61dac..837e2a17bb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -47,6 +47,7 @@ import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.core.util.NamedThreadFactory; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; @@ -98,30 +99,30 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { @Override public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback) { - if (request.getObjectId() != null && client.isValidObjectVersion(request.getVersionedId())) { - ReadRequest downlink = new ReadRequest(getContentFormat(client, request), request.getObjectId()); - sendRequest(client, downlink, request.getTimeout(), callback); - } + validateVersionedId(client, request); + ReadRequest downlink = new ReadRequest(getContentFormat(client, request), request.getObjectId()); + sendRequest(client, downlink, request.getTimeout(), callback); } @Override public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback) { - if (request.getObjectId() != null && client.isValidObjectVersion(request.getVersionedId())) { - LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); - Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); - if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { - ObserveRequest downlink; - ContentFormat contentFormat = getContentFormat(client, request); - if (resultIds.isResource()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); - } else if (resultIds.isObjectInstance()) { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); - } else { - downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); - } - log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); - sendRequest(client, downlink, request.getTimeout(), callback); + validateVersionedId(client, request); + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); + Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); + if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { + ObserveRequest downlink; + ContentFormat contentFormat = getContentFormat(client, request); + if (resultIds.isResource()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + } else if (resultIds.isObjectInstance()) { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + } else { + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); } + log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); + sendRequest(client, downlink, request.getTimeout(), callback); + } else { + throw new IllegalArgumentException("Observation is already registered!"); } } @@ -133,10 +134,8 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } @Override - public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { - Link[] objectLinks = client.getRegistration().getSortedObjectLinks(); - Set paths = Arrays.stream(objectLinks).map(Link::toString).collect(Collectors.toUnmodifiableSet()); - callback.onSuccess(paths); + public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { + callback.onSuccess(Arrays.asList(client.getRegistration().getSortedObjectLinks())); } @Override @@ -172,27 +171,28 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { @Override public void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback) { - if (request.getObjectId() != null && client.isValidObjectVersion(request.getVersionedId())) { - sendRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback); - } + validateVersionedId(client, request); + sendRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback); } @Override public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback) { - if (request.getObjectId() != null && client.isValidObjectVersion(request.getVersionedId()) && request.getAttributes() != null) { - ObjectAttributes params = request.getAttributes(); - List attributes = new LinkedList<>(); + validateVersionedId(client, request); + if (request.getAttributes() == null) { + throw new IllegalArgumentException("Attributes to write are not specified!"); + } + ObjectAttributes params = request.getAttributes(); + List attributes = new LinkedList<>(); // Dimension and Object version are read only attributes. // addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255); // addAttribute(attributes, OBJECT_VERSION, params.getVer(), StringUtils::isNotEmpty, Function.identity()); - addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); - addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); - addAttribute(attributes, GREATER_THAN, params.getGt()); - addAttribute(attributes, LESSER_THAN, params.getLt()); - addAttribute(attributes, STEP, params.getSt()); - AttributeSet attributeSet = new AttributeSet(attributes); - sendRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); - } + addAttribute(attributes, MAXIMUM_PERIOD, params.getPmax()); + addAttribute(attributes, MINIMUM_PERIOD, params.getPmin()); + addAttribute(attributes, GREATER_THAN, params.getGt()); + addAttribute(attributes, LESSER_THAN, params.getLt()); + addAttribute(attributes, STEP, params.getSt()); + AttributeSet attributeSet = new AttributeSet(attributes); + sendRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); } @Override @@ -642,7 +642,14 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { // handler.sentRpcResponse(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO); // } // } - + private void validateVersionedId(LwM2mClient client, HasVersionedId request) { + if (!client.isValidObjectVersion(request.getVersionedId())) { + throw new IllegalArgumentException("Specified resource id is not configured in the device profile!"); + } + if (request.getObjectId() == null) { + throw new IllegalArgumentException("Specified object id is null!"); + } + } private static void addAttribute(List attributes, String attributeName, T value) { addAttribute(attributes, attributeName, value, null, null); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java index 473205eaa1..724dc78993 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -24,6 +25,7 @@ import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import java.util.List; import java.util.Set; public interface LwM2mDownlinkMsgHandler { @@ -44,7 +46,7 @@ public interface LwM2mDownlinkMsgHandler { void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback); - void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); + void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java similarity index 84% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceCallback.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java index c8ef5622f2..2cee4887c9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java @@ -19,11 +19,11 @@ import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MWriteReplaceCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MWriteResponseCallback extends AbstractTbLwM2MRequestCallback { private final String targetId; - public TbLwM2MWriteReplaceCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + public TbLwM2MWriteResponseCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { super(handler, client); this.targetId = targetId; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 4da5409712..0c371dac4e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -29,8 +29,27 @@ import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; 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.downlink.LwM2mDownlinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllObserveCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDeleteRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverAllRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveAllRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteUpdateRequest; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Map; @@ -57,62 +76,167 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private final LwM2mDownlinkMsgHandler downlinkHandler; private final Map rpcSubscriptions = new ConcurrentHashMap<>(); - @Override public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg rpcRequst, TransportProtos.SessionInfoProto sessionInfo) { this.cleanupOldSessions(); UUID requestUUID = new UUID(rpcRequst.getRequestIdMSB(), rpcRequst.getRequestIdLSB()); log.warn("Received params: {}", rpcRequst.getParams()); - // TODO: We use this map to protect from browser issue that the same command is sent twice. This is probably not the best place and should be moved to DeviceActor + // We use this map to protect from browser issue that the same command is sent twice. + // TODO: This is probably not the best place and should be moved to DeviceActor if (!this.rpcSubscriptions.containsKey(requestUUID)) { LwM2mOperationType operationType = LwM2mOperationType.fromType(rpcRequst.getMethodName()); if (operationType == null) { this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.METHOD_NOT_ALLOWED.getName(), "Unsupported operation type: " + rpcRequst.getMethodName()); + return; } LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); if (client.getRegistration() == null) { this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR.getName(), "Registration is empty"); + return; } - switch (operationType) { - case READ: - sendReadRequest(client, rpcRequst); - break; + try { + if (operationType.isHasObjectId()) { + String objectId = getIdFromParameters(client, rpcRequst); + switch (operationType) { + case READ: + sendReadRequest(client, rpcRequst, objectId); + break; + case OBSERVE: + sendObserveRequest(client, rpcRequst, objectId); + break; + case DISCOVER: + sendDiscoverRequest(client, rpcRequst, objectId); + break; + case EXECUTE: + sendExecuteRequest(client, rpcRequst, objectId); + break; + case WRITE_ATTRIBUTES: + sendWriteAttributesRequest(client, rpcRequst, objectId); + break; + case OBSERVE_CANCEL: + sendCancelObserveRequest(client, rpcRequst, objectId); + break; + case DELETE: + sendDeleteRequest(client, rpcRequst, objectId); + break; + case WRITE_UPDATE: + sendWriteUpdateRequest(client, rpcRequst, objectId); + break; + case WRITE_REPLACE: + sendWriteReplaceRequest(client, rpcRequst, objectId); + break; + default: + throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); + } + } else { + switch (operationType) { + case OBSERVE_CANCEL_ALL: + sendCancelAllObserveRequest(client, rpcRequst); + break; + case OBSERVE_READ_ALL: + sendObserveAllRequest(client, rpcRequst); + break; + case DISCOVER_ALL: + sendDiscoverAllRequest(client, rpcRequst); + break; + case FW_UPDATE: + //TODO: implement and add break statement + default: + throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); + } + } + } catch (IllegalArgumentException e) { + this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.BAD_REQUEST.getName(), e.getMessage()); } + } + } + private void sendReadRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MReadCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcReadResponseCallback<>(transportService, client, requestMsg, versionedId, mainCallback); + downlinkHandler.sendReadRequest(client, request, rpcCallback); + } -// log.warn("4) rpcRequst: [{}], sessionUUID: [{}]", rpcRequst, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); -// String bodyParams = StringUtils.trimToNull(rpcRequst.getParams()) != null ? rpcRequst.getParams() : "null"; -// LwM2mOperationType lwM2mTypeOper = setValidTypeOper(rpcRequst.getMethodName()); -// this.rpcSubscriptions.put(requestUUID, rpcRequst.getExpirationTime()); -// LwM2mClientRpcRequest lwm2mClientRpcRequest = null; -// try { -// LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); -// Registration registration = client.getRegistration(); -// if (registration != null) { -// lwm2mClientRpcRequest = new LwM2mClientRpcRequest(lwM2mTypeOper, bodyParams, rpcRequst.getRequestId(), sessionInfo, registration, uplinkHandler); -// if (lwm2mClientRpcRequest.getErrorMsg() != null) { -// lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); -// this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); -// } else { -// //TODO: use different methods and RPC callback wrapper. -//// defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), -//// null, -//// lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), -//// this.config.getTimeout(), lwm2mClientRpcRequest); -// } -// } else { -// this.sendErrorRpcResponse(lwm2mClientRpcRequest, "registration == null", sessionInfo); -// } -// } catch (Exception e) { -// this.sendErrorRpcResponse(lwm2mClientRpcRequest, e.getMessage(), sessionInfo); -// } - } + private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + TbLwM2MObserveRequest request = TbLwM2MObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MObserveCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcReadResponseCallback<>(transportService, client, requestMsg, versionedId, mainCallback); + downlinkHandler.sendObserveRequest(client, request, rpcCallback); + } + + private void sendObserveAllRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { + TbLwM2MObserveAllRequest request = TbLwM2MObserveAllRequest.builder().timeout(this.config.getTimeout()).build(); + downlinkHandler.sendObserveAllRequest(client, request, new RpcLinkSetCallback(transportService, client, requestMsg, null)); + } + + private void sendDiscoverAllRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { + TbLwM2MDiscoverAllRequest request = TbLwM2MDiscoverAllRequest.builder().timeout(this.config.getTimeout()).build(); + downlinkHandler.sendDiscoverAllRequest(client, request, new RpcLinkSetCallback(transportService, client, requestMsg, null)); + } + + private void sendDiscoverRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MDiscoverCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcDiscoverCallback(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendDiscoverRequest(client, request, rpcCallback); + } + + private void sendExecuteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MExecuteCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendExecuteRequest(client, downlink, rpcCallback); + } + + private void sendWriteAttributesRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + RpcWriteAttributesRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteAttributesRequest.class); + TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(versionedId) + .attributes(requestBody.getAttributes()) + .timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MWriteAttributesCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendWriteAttributesRequest(client, request, rpcCallback); + } + + private void sendWriteUpdateRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + RpcWriteUpdateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteUpdateRequest.class); + TbLwM2MWriteUpdateRequest.TbLwM2MWriteUpdateRequestBuilder builder = TbLwM2MWriteUpdateRequest.builder().versionedId(versionedId); + builder.value(requestBody.getValue()).timeout(this.config.getTimeout()); + var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendWriteUpdateRequest(client, builder.build(), rpcCallback); + } + + private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + RpcWriteUpdateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteUpdateRequest.class); + TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) + .value(requestBody.getValue()) + .timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendWriteReplaceRequest(client, request, rpcCallback); + } + + private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + TbLwM2MCancelObserveRequest downlink = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MCancelObserveCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcCancelObserveCallback(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendCancelObserveRequest(client, downlink, rpcCallback); + } + + private void sendDeleteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { + TbLwM2MDeleteRequest downlink = TbLwM2MDeleteRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MDeleteCallback(uplinkHandler, client, versionedId); + var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendDeleteRequest(client, downlink, rpcCallback); } - private void sendReadRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { - String id = getIdFromParameters(client, rpcRequst); - TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(id).timeout(this.config.getTimeout()).build(); - downlinkHandler.sendReadRequest(client, request, new TbLwM2MReadCallback(uplinkHandler, client, id)); + private void sendCancelAllObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { + TbLwM2MCancelAllRequest downlink = TbLwM2MCancelAllRequest.builder().timeout(this.config.getTimeout()).build(); + var mainCallback = new TbLwM2MCancelAllObserveCallback(uplinkHandler, client); + var rpcCallback = new RpcCancelObserveCallback(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback); } private String getIdFromParameters(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcResponseBody.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcResponseBody.java new file mode 100644 index 0000000000..16b743101b --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcResponseBody.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.rpc; + +import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LwM2MRpcResponseBody { + + private String result; + private String value; + private String error; + private String info; + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java new file mode 100644 index 0000000000..8fafa294ee --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java @@ -0,0 +1,41 @@ +/** + * 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.rpc; + +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.LwM2mObject; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + +import java.util.Optional; + +public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy { + + public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + } + + @Override + protected void sendRpcReplyOnSuccess(Integer response) { + reply(LwM2MRpcResponseBody.builder().result(ResponseCode.CONTENT.getName()).value(response.toString()).build()); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java new file mode 100644 index 0000000000..54c621d8f9 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.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.transport.lwm2m.server.rpc; + +import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.node.LwM2mObject; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.jetbrains.annotations.NotNull; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + +import java.util.Optional; + +public class RpcDiscoverCallback extends RpcLwM2MDownlinkCallback { + + public RpcDiscoverCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + } + + protected Optional serializeSuccessfulResponse(DiscoverResponse response) { + return Optional.of(Link.serialize(response.getObjectLinks())); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java new file mode 100644 index 0000000000..050e265b34 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java @@ -0,0 +1,86 @@ +/** + * 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.rpc; + +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.node.codec.LwM2mValueConverter; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; +import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; + +public abstract class RpcDownlinkRequestCallbackProxy implements DownlinkRequestCallback { + + private final TransportService transportService; + private final TransportProtos.ToDeviceRpcRequestMsg request; + private final DownlinkRequestCallback callback; + + protected final LwM2mClient client; + protected final LwM2mValueConverter converter; + + public RpcDownlinkRequestCallbackProxy(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + this.transportService = transportService; + this.client = client; + this.request = requestMsg; + this.callback = callback; + this.converter = LwM2mValueConverterImpl.getInstance(); + } + + @Override + public void onSuccess(T response) { + sendRpcReplyOnSuccess(response); + if (callback != null) { + callback.onSuccess(response); + } + } + + @Override + public void onValidationError(String msg) { + sendRpcReplyOnValidationError(msg); + if (callback != null) { + callback.onValidationError(msg); + } + } + + @Override + public void onError(Exception e) { + sendRpcReplyOnError(e); + if (callback != null) { + callback.onError(e); + } + } + + protected void reply(LwM2MRpcResponseBody response) { + TransportProtos.ToDeviceRpcResponseMsg msg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() + .setPayload(JacksonUtil.toString(response)) + .setRequestId(request.getRequestId()) + .build(); + transportService.process(client.getSession(), msg, null); + } + + abstract protected void sendRpcReplyOnSuccess(T response); + + protected void sendRpcReplyOnValidationError(String msg) { + reply(LwM2MRpcResponseBody.builder().result(ResponseCode.BAD_REQUEST.getName()).error(msg).build()); + } + + protected void sendRpcReplyOnError(Exception e) { + reply(LwM2MRpcResponseBody.builder().result(ResponseCode.INTERNAL_SERVER_ERROR.getName()).error(e.getMessage()).build()); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcEmptyResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcEmptyResponseCallback.java new file mode 100644 index 0000000000..a5f075b258 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcEmptyResponseCallback.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.server.rpc; + +import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + +import java.util.Optional; + +public class RpcEmptyResponseCallback extends RpcLwM2MDownlinkCallback { + + public RpcEmptyResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + } + + protected Optional serializeSuccessfulResponse(T response) { + return Optional.empty(); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java new file mode 100644 index 0000000000..bcb5b2ead5 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java @@ -0,0 +1,41 @@ +/** + * 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.rpc; + +import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + +import java.util.Optional; +import java.util.Set; + +public class RpcLinkSetCallback extends RpcDownlinkRequestCallbackProxy { + + public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + } + + @Override + protected void sendRpcReplyOnSuccess(T response) { + reply(LwM2MRpcResponseBody.builder().result(ResponseCode.CONTENT.getName()).value(JacksonUtil.toString(response)).build()); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLwM2MDownlinkCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLwM2MDownlinkCallback.java new file mode 100644 index 0000000000..c8d32ef184 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLwM2MDownlinkCallback.java @@ -0,0 +1,50 @@ +/** + * 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.rpc; + +import org.eclipse.leshan.core.response.LwM2mResponse; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + +import java.util.Optional; + +public abstract class RpcLwM2MDownlinkCallback extends RpcDownlinkRequestCallbackProxy { + + public RpcLwM2MDownlinkCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + } + + @Override + protected void sendRpcReplyOnSuccess(T response) { + LwM2MRpcResponseBody.LwM2MRpcResponseBodyBuilder builder = LwM2MRpcResponseBody.builder().result(response.getCode().getName()); + if (response.isSuccess()) { + Optional responseValue = serializeSuccessfulResponse(response); + if (responseValue.isPresent() && StringUtils.isNotEmpty(responseValue.get())) { + builder.value(responseValue.get()); + } + } else { + if (StringUtils.isNotEmpty(response.getErrorMessage())) { + builder.error(response.getErrorMessage()); + } + } + reply(builder.build()); + } + + protected abstract Optional serializeSuccessfulResponse(T response); +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java new file mode 100644 index 0000000000..6a4ed06d6b --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java @@ -0,0 +1,50 @@ +/** + * 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.rpc; + +import org.eclipse.leshan.core.node.LwM2mObject; +import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + +import java.util.Optional; + +public class RpcReadResponseCallback extends RpcLwM2MDownlinkCallback { + + private final String versionedId; + + public RpcReadResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + this.versionedId = versionedId; + } + + @Override + protected Optional serializeSuccessfulResponse(T response) { + Object value = null; + if (response.getContent() instanceof LwM2mObject) { + value = client.objectToString((LwM2mObject) response.getContent(), this.converter, versionedId); + } else if (response.getContent() instanceof LwM2mObjectInstance) { + value = client.instanceToString((LwM2mObjectInstance) response.getContent(), this.converter, versionedId); + } else if (response.getContent() instanceof LwM2mResource) { + value = client.resourceToString((LwM2mResource) response.getContent(), this.converter, versionedId); + } + return Optional.of(String.format("%s", value)); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.java new file mode 100644 index 0000000000..67642078c6 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteAttributesRequest.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.transport.lwm2m.server.rpc; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RpcWriteAttributesRequest extends IdOrKeyRequest { + + private ObjectAttributes attributes; + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.java new file mode 100644 index 0000000000..55fb2b49aa --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteReplaceRequest.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.rpc; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RpcWriteReplaceRequest extends IdOrKeyRequest { + + private Object value; + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.java new file mode 100644 index 0000000000..b56fea7eb6 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcWriteUpdateRequest.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.transport.lwm2m.server.rpc; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; + +@Data +@EqualsAndHashCode(callSuper = true) +public class RpcWriteUpdateRequest extends IdOrKeyRequest { + + private Object value; + private String contentFormat; + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 084af742ca..5187c4c493 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -80,7 +80,7 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadCallbac import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler; import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2mClientRpcRequest; @@ -115,7 +115,6 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_WARN; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.READ; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @@ -333,29 +332,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { this.updateResourcesValue(registration, lwM2mResource, path); } } - if (rpcRequest != null) { - //TODO: move this to separate callback. - this.sendRpcRequestAfterReadResponse(registration, lwM2MClient, path, response, rpcRequest); - } - } - } - - private void sendRpcRequestAfterReadResponse(Registration registration, LwM2mClient lwM2MClient, String pathIdVer, ReadResponse response, - LwM2mClientRpcRequest rpcRequest) { - Object value = null; - if (response.getContent() instanceof LwM2mObject) { - value = lwM2MClient.objectToString((LwM2mObject) response.getContent(), this.converter, pathIdVer); - } else if (response.getContent() instanceof LwM2mObjectInstance) { - value = lwM2MClient.instanceToString((LwM2mObjectInstance) response.getContent(), this.converter, pathIdVer); - } else if (response.getContent() instanceof LwM2mResource) { - value = lwM2MClient.resourceToString((LwM2mResource) response.getContent(), this.converter, pathIdVer); } - String msg = String.format("%s: type operation %s path - %s value - %s", LOG_LW2M_INFO, - READ, pathIdVer, value); - this.sendLogsToThingsboard(lwM2MClient, msg); - rpcRequest.setValueMsg(String.format("%s", value)); - //TODO: refactor -// this.sentRpcResponse(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); } /** @@ -964,7 +941,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteReplaceCallback(this, lwM2MClient, versionedId)); + defaultLwM2MDownlinkMsgHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(this, lwM2MClient, versionedId)); } else { log.error("Failed update resource [{}] [{}]", versionedId, newValue); String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", From 810c2a7ed3af17887481382a57cb7b06a54a1a6d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 14 Jun 2021 18:57:02 +0300 Subject: [PATCH 372/476] UI: Fixed bug, not disabled deleting alias used only in alarm widgets --- .../alias/entity-aliases-dialog.component.ts | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts index 08119e6ce8..aeb0f9c286 100644 --- a/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alias/entity-aliases-dialog.component.ts @@ -98,24 +98,17 @@ export class EntityAliasesDialogComponent extends DialogComponent { if (widget.type === widgetType.rpc) { if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) { - const targetDeviceAliasId = widget.config.targetDeviceAliasIds[0]; - widgetsTitleList = this.aliasToWidgetsMap[targetDeviceAliasId]; - if (!widgetsTitleList) { - widgetsTitleList = []; - this.aliasToWidgetsMap[targetDeviceAliasId] = widgetsTitleList; - } - widgetsTitleList.push(widget.config.title); + this.addWidgetTitleToWidgetsMap(widget.config.targetDeviceAliasIds[0], widget.config.title); + } + } else if (widget.type === widgetType.alarm) { + if (widget.config.alarmSource) { + this.addWidgetTitleToWidgetsMap(widget.config.alarmSource.entityAliasId, widget.config.title); } } else { const datasources = this.utils.validateDatasources(widget.config.datasources); datasources.forEach((datasource) => { if (datasource.type === DatasourceType.entity && datasource.entityAliasId) { - widgetsTitleList = this.aliasToWidgetsMap[datasource.entityAliasId]; - if (!widgetsTitleList) { - widgetsTitleList = []; - this.aliasToWidgetsMap[datasource.entityAliasId] = widgetsTitleList; - } - widgetsTitleList.push(widget.config.title); + this.addWidgetTitleToWidgetsMap(datasource.entityAliasId, widget.config.title); } }); } @@ -141,6 +134,15 @@ export class EntityAliasesDialogComponent extends DialogComponent = this.aliasToWidgetsMap[aliasId]; + if (!widgetsTitleList) { + widgetsTitleList = []; + this.aliasToWidgetsMap[aliasId] = widgetsTitleList; + } + widgetsTitleList.push(widgetTitle); + } + private createEntityAliasFormControl(aliasId: string, entityAlias: EntityAlias): AbstractControl { const aliasFormControl = this.fb.group({ id: [aliasId], From 095e8913ac5fff1de993486d88998d7b0c3dfd7a Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 14 Jun 2021 19:39:22 +0300 Subject: [PATCH 373/476] UI: Revert code onRowClick function in table --- .../widget/lib/alarms-table-widget.component.ts | 8 ++++---- .../widget/lib/entities-table-widget.component.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 7c7b006159..da9608e88c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -720,12 +720,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, } public onRowClick($event: Event, alarm: AlarmDataInfo) { + if ($event) { + $event.stopPropagation(); + } + this.alarmsDatasource.toggleCurrentAlarm(alarm); const descriptors = this.ctx.actionsApi.getActionDescriptors('rowClick'); if (descriptors.length) { - if ($event) { - $event.stopPropagation(); - } - this.alarmsDatasource.toggleCurrentAlarm(alarm); let entityId; let entityName; if (alarm && alarm.originator) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index 470697c615..f1e554bb2b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -661,13 +661,13 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni } public onRowClick($event: Event, entity: EntityData, isDouble?: boolean) { + if ($event) { + $event.stopPropagation(); + } + this.entityDatasource.toggleCurrentEntity(entity); const actionSourceId = isDouble ? 'rowDoubleClick' : 'rowClick'; const descriptors = this.ctx.actionsApi.getActionDescriptors(actionSourceId); if (descriptors.length) { - if ($event) { - $event.stopPropagation(); - } - this.entityDatasource.toggleCurrentEntity(entity); let entityId; let entityName; let entityLabel; From 6d025d6d5e0edc6274bafc5a974491afe732e200 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 15 Jun 2021 13:45:36 +0300 Subject: [PATCH 374/476] Logging using callbacks --- .../secure/LwM2MBootstrapSecurityStore.java | 22 +- .../lwm2m/server/LwM2mServerListener.java | 13 +- .../lwm2m/server/LwM2mTransportUtil.java | 10 +- .../lwm2m/server/client/LwM2mClient.java | 7 +- .../lwm2m/server/client/LwM2mFwSwUpdate.java | 29 ++- .../AbstractTbLwM2MRequestCallback.java | 18 +- .../DefaultLwM2mDownlinkMsgHandler.java | 207 ++---------------- .../downlink/DownlinkRequestCallback.java | 19 +- .../downlink/LwM2mDownlinkMsgHandler.java | 31 ++- .../TbLwM2MCancelAllObserveCallback.java | 8 +- .../TbLwM2MCancelObserveCallback.java | 8 +- .../downlink/TbLwM2MDeleteCallback.java | 15 +- .../downlink/TbLwM2MDiscoverCallback.java | 13 +- .../downlink/TbLwM2MExecuteCallback.java | 5 +- .../downlink/TbLwM2MObserveCallback.java | 18 +- .../server/downlink/TbLwM2MReadCallback.java | 14 +- .../downlink/TbLwM2MTargetedCallback.java | 44 ++++ .../TbLwM2MWriteAttributesCallback.java | 5 +- .../TbLwM2MWriteResponseCallback.java | 7 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 18 +- .../rpc/RpcCancelAllObserveCallback.java | 36 +++ .../server/rpc/RpcCancelObserveCallback.java | 5 +- .../lwm2m/server/rpc/RpcDiscoverCallback.java | 5 +- .../rpc/RpcDownlinkRequestCallbackProxy.java | 18 +- .../server/rpc/RpcEmptyResponseCallback.java | 7 +- .../lwm2m/server/rpc/RpcLinkSetCallback.java | 4 +- .../server/rpc/RpcLwM2MDownlinkCallback.java | 5 +- .../server/rpc/RpcReadResponseCallback.java | 5 +- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 81 +++---- .../server/uplink/LwM2mUplinkMsgHandler.java | 7 +- 30 files changed, 299 insertions(+), 385 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.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 c23cc103df..310d738757 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 @@ -15,9 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.bootstrap.secure; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.JsonObject; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.util.Hex; @@ -45,12 +42,9 @@ import java.util.Collections; import java.util.Iterator; import java.util.UUID; -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.LOG_LW2M_TELEMETRY; -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.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard; @Slf4j @@ -165,14 +159,14 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { 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()); - helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo); + String logMsg = String.format("%s: getParametersBootstrap: %s Access connect client with bootstrap server.", LOG_LWM2M_INFO, store.getEndpoint()); + helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, 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()); - helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), sessionInfo); + log.error("{} getParametersBootstrap: [{}] Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint()); + String logMsg = String.format("%s: getParametersBootstrap: %s Different values SecurityMode between of client and profile.", LOG_LWM2M_ERROR, store.getEndpoint()); + helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, 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 97649603a6..0d566974d2 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 @@ -27,7 +27,7 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle import java.util.Collection; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @Slf4j @@ -87,9 +87,7 @@ public class LwM2mServerListener { @Override public void cancelled(Observation observation) { - String msg = String.format("%s: Canceled Observation %s.", LOG_LW2M_INFO, observation.getPath()); - service.sendLogsToThingsboard(observation.getRegistrationId(), msg); - log.warn(msg); + log.trace("Canceled Observation {}.", observation.getPath()); } @Override @@ -102,15 +100,12 @@ public class LwM2mServerListener { @Override public void onError(Observation observation, Registration registration, Exception error) { - log.error(String.format("Unable to handle notification of [%s:%s]", observation.getRegistrationId(), observation.getPath()), error); + log.error("Unable to handle notification of [{}:{}]", observation.getRegistrationId(), observation.getPath(), error); } @Override public void newObservation(Observation observation, Registration registration) { - String msg = String.format("%s: Successful start newObservation %s.", LOG_LW2M_INFO, - observation.getPath()); - log.warn(msg); - service.sendLogsToThingsboard(registration.getId(), msg); + log.trace("Successful start newObservation {}.", observation.getPath()); } }; } 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 be5fea6df6..0d8b96e74b 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 @@ -111,11 +111,11 @@ public class LwM2mTransportUtil { public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms - public static final String LOG_LW2M_TELEMETRY = "logLwm2m"; - public static final String LOG_LW2M_INFO = "info"; - public static final String LOG_LW2M_ERROR = "error"; - public static final String LOG_LW2M_WARN = "warn"; - public static final String LOG_LW2M_VALUE = "value"; + public static final String LOG_LWM2M_TELEMETRY = "logLwm2m"; + public static final String LOG_LWM2M_INFO = "info"; + public static final String LOG_LWM2M_ERROR = "error"; + public static final String LOG_LWM2M_WARN = "warn"; + public static final String LOG_LWM2M_VALUE = "value"; public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized"; public static final String LWM2M_VERSION_DEFAULT = "1.0"; 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 77454d8b83..80e85036c3 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,7 +20,6 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.model.ObjectModel; 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; @@ -38,8 +37,8 @@ import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Collection; @@ -56,13 +55,11 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; -import static org.eclipse.leshan.core.model.ResourceModel.Type.OPAQUE; -import static org.eclipse.leshan.core.model.ResourceModel.Type.STRING; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId; @Slf4j diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java index 6896c3d8de..000e97ccc1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java @@ -61,8 +61,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.F import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_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_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL; @@ -124,7 +124,6 @@ public class LwM2mFwSwUpdate { @Getter LwM2mClient lwM2MClient; @Getter - @Setter private final List pendingInfoRequestsStart; @Getter @Setter @@ -189,11 +188,11 @@ public class LwM2mFwSwUpdate { public void writeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) { if (this.currentId != null) { this.stateUpdate = OtaPackageUpdateStatus.INITIATED.name(); - this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); + this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LWM2M_INFO, null); String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration()); - String fwMsg = String.format("%s: Start type operation %s paths: %s", LOG_LW2M_INFO, + String fwMsg = String.format("%s: Start type operation %s paths: %s", LOG_LWM2M_INFO, LwM2mOperationType.FW_UPDATE.name(), this.pathPackageId); - handler.sendLogsToThingsboard(fwMsg, lwM2MClient.getRegistration().getId()); + handler.logToTelemetry(fwMsg, lwM2MClient.getRegistration().getId()); log.warn("8) Start firmware Update. Send save to: [{}] ver: [{}] path: [{}]", this.lwM2MClient.getDeviceName(), this.currentVersion, targetIdVer); if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy) { int chunkSize = 0; @@ -220,7 +219,7 @@ public class LwM2mFwSwUpdate { // handler.sentRpcResponse(this.rpcRequest, CONTENT.name(), msgError, LOG_LW2M_ERROR); } log.error(msgError); - this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); + this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LWM2M_ERROR, msgError); } } @@ -228,10 +227,10 @@ public class LwM2mFwSwUpdate { // this.sendSateOnThingsBoard(handler); String msg = String.format("%s: %s, %s, pkgVer: %s: pkgName - %s state - %s.", typeInfo, this.wUpdate, typeOper, this.currentVersion, this.currentTitle, this.stateUpdate); - if (LOG_LW2M_ERROR.equals(typeInfo)) { + if (LOG_LWM2M_ERROR.equals(typeInfo)) { msg = String.format("%s Error: %s", msg, msgError); } - handler.sendLogsToThingsboard(lwM2MClient, msg); + handler.logToTelemetry(lwM2MClient, msg); } @@ -241,7 +240,7 @@ public class LwM2mFwSwUpdate { * send execute */ public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) { - this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); + this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_INFO, null); //TODO: user this.rpcRequest??? TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(pathInstallId).timeout(handler.config.getTimeout()).build(); request.sendExecuteRequest(lwM2MClient, downlink, new TbLwM2MExecuteCallback(handler, lwM2MClient, pathInstallId)); @@ -272,14 +271,14 @@ public class LwM2mFwSwUpdate { ver3 != null && ver3.contains(this.currentVersion) )) || (this.currentTitle != null && pathName != null && this.currentTitle.equals(pathName))) { - fwMsg = String.format("%s: The update was interrupted. The device has the same version: %s.", LOG_LW2M_ERROR, + fwMsg = String.format("%s: The update was interrupted. The device has the same version: %s.", LOG_LWM2M_ERROR, this.currentVersion); } else if (updateResultFw != null && updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { - fwMsg = String.format("%s: The update was interrupted. The device has the status UpdateResult: error (%d).", LOG_LW2M_ERROR, + fwMsg = String.format("%s: The update was interrupted. The device has the status UpdateResult: error (%d).", LOG_LWM2M_ERROR, updateResultFw); } if (fwMsg != null) { - handler.sendLogsToThingsboard(fwMsg, lwM2MClient.getRegistration().getId()); + handler.logToTelemetry(fwMsg, lwM2MClient.getRegistration().getId()); return false; } else { return true; @@ -364,10 +363,10 @@ public class LwM2mFwSwUpdate { String key = splitCamelCaseString((String) this.lwM2MClient.getResourceNameByRezId(null, this.pathResultId)); if (success) { this.stateUpdate = OtaPackageUpdateStatus.UPDATED.name(); - this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); + this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_INFO, null); } else { this.stateUpdate = OtaPackageUpdateStatus.FAILED.name(); - this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_ERROR, value); + this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_ERROR, value); } handler.helper.sendParametersOnThingsboardTelemetry( handler.helper.getKvStringtoThingsboard(key, value), this.lwM2MClient.getSession()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java index 66da1c3d38..764d5ff5ef 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java @@ -15,10 +15,14 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; + +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; -public abstract class AbstractTbLwM2MRequestCallback implements DownlinkRequestCallback { +@Slf4j +public abstract class AbstractTbLwM2MRequestCallback implements DownlinkRequestCallback { protected final LwM2mUplinkMsgHandler handler; protected final LwM2mClient client; @@ -29,12 +33,14 @@ public abstract class AbstractTbLwM2MRequestCallback implements DownlinkReque } @Override - public void onValidationError(String msg) { - + public void onValidationError(String params, String msg) { + log.trace("[{}] Request [{}] validation failed. Reason: {}", client.getEndpoint(), params, msg); + handler.logToTelemetry(client, String.format("[%s]: Request [%s] validation failed. Reason: %s", LOG_LWM2M_WARN, params, msg)); } @Override - public void onError(Exception e) { - + public void onError(String params, Exception e) { + log.trace("[{}] Request [{}] processing failed", client.getEndpoint(), params, e); + handler.logToTelemetry(client, String.format("[%s]: Request [%s] processing failed. Reason: %s", LOG_LWM2M_WARN, params, e)); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index 837e2a17bb..746b985812 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -49,6 +49,7 @@ import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; @@ -88,7 +89,6 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - private final LwM2mClientContext lwM2mClientContext; @PostConstruct public void init() { @@ -98,14 +98,14 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } @Override - public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback) { + public void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback) { validateVersionedId(client, request); ReadRequest downlink = new ReadRequest(getContentFormat(client, request), request.getObjectId()); sendRequest(client, downlink, request.getTimeout(), callback); } @Override - public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback) { + public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback) { validateVersionedId(client, request); LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); @@ -127,19 +127,19 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } @Override - public void sendObserveAllRequest(LwM2mClient client, TbLwM2MObserveAllRequest request, DownlinkRequestCallback> callback) { + public void sendObserveAllRequest(LwM2mClient client, TbLwM2MObserveAllRequest request, DownlinkRequestCallback> callback) { Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); Set paths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); - callback.onSuccess(paths); + callback.onSuccess(request, paths); } @Override - public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { - callback.onSuccess(Arrays.asList(client.getRegistration().getSortedObjectLinks())); + public void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback) { + callback.onSuccess(request, Arrays.asList(client.getRegistration().getSortedObjectLinks())); } @Override - public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback) { + public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback) { ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); if (resourceModelExecute != null) { ExecuteRequest downlink; @@ -153,30 +153,30 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } @Override - public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback callback) { + public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback callback) { sendRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback); } @Override - public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback) { + public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback) { int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); - callback.onSuccess(observeCancelCnt); + callback.onSuccess(request, observeCancelCnt); } @Override - public void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback callback) { + public void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback callback) { int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration()); - callback.onSuccess(observeCancelCnt); + callback.onSuccess(request, observeCancelCnt); } @Override - public void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback) { + public void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback) { validateVersionedId(client, request); sendRequest(client, new DiscoverRequest(request.getObjectId()), request.getTimeout(), callback); } @Override - public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback) { + public void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback) { validateVersionedId(client, request); if (request.getAttributes() == null) { throw new IllegalArgumentException("Attributes to write are not specified!"); @@ -196,7 +196,7 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } @Override - public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback) { + public void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback) { ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); if (resourceModelWrite != null) { ContentFormat contentFormat = convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); @@ -206,15 +206,15 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), request.getValue()); sendRequest(client, downlink, request.getTimeout(), callback); } catch (Exception e) { - callback.onError(e); + callback.onError(JacksonUtil.toString(request), e); } } else { - //TODO: log validation error using callback. + callback.onValidationError(JacksonUtil.toString(request), "Resource " + request.getVersionedId() + " is not configured in the device profile!"); } } @Override - public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback) { + public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback) { LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); if (resultIds.isResource()) { /* @@ -238,186 +238,27 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); sendRequest(client, downlink, request.getTimeout(), callback); } else { - callback.onValidationError("No resources to update!"); + callback.onValidationError(JacksonUtil.toString(request), "No resources to update!"); } } else { - callback.onValidationError("Update of the root level object is not supported yet!"); + callback.onValidationError(JacksonUtil.toString(request), "Update of the root level object is not supported yet!"); } } -// public void sendAllRequest(LwM2mClient client, String targetIdVer, LwM2mTypeOper typeOper, -// ContentFormat contentFormat, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { -// Registration registration = client.getRegistration(); -// try { -// String target = fromVersionedIdToObjectId(targetIdVer); -// if (contentFormat == null) { -// contentFormat = client.getDefaultContentFormat(); -// } -// LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null; -// if (!OBSERVE_CANCEL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0) { -// if (client.isValidObjectVersion(targetIdVer)) { -// timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; -// SimpleDownlinkRequest request = createRequest(registration, client, typeOper, contentFormat, target, -// targetIdVer, resultIds, params, lwm2mClientRpcRequest); -// if (request != null) { -// try { -// this.sendRequest(client, request, timeoutInMs, lwm2mClientRpcRequest); -// } catch (ClientSleepingException e) { -// SimpleDownlinkRequest finalRequest = request; -// long finalTimeoutInMs = timeoutInMs; -// LwM2mClientRpcRequest finalRpcRequest = lwm2mClientRpcRequest; -// client.getQueuedRequests().add(() -> sendRequest(client, finalRequest, finalTimeoutInMs, finalRpcRequest)); -// } catch (Exception e) { -// log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e); -// } -// } else if (WRITE_UPDATE.name().equals(typeOper.name())) { -// if (lwm2mClientRpcRequest != null) { -// String errorMsg = String.format("Path %s params is not valid", targetIdVer); -// handler.sentRpcResponse(lwm2mClientRpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); -// } -// } else if (WRITE_REPLACE.name().equals(typeOper.name()) || EXECUTE.name().equals(typeOper.name())) { -// if (lwm2mClientRpcRequest != null) { -// String errorMsg = String.format("Path %s object model is absent", targetIdVer); -// handler.sentRpcResponse(lwm2mClientRpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); -// } -// } else if (!OBSERVE_CANCEL.name().equals(typeOper.name())) { -// log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer); -// if (lwm2mClientRpcRequest != null) { -// ResourceModel resourceModel = client.getResourceModel(targetIdVer, this.config.getModelProvider()); -// String errorMsg = resourceModel == null ? String.format("Path %s not found in object version", targetIdVer) : "SendRequest - null"; -// handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); -// } -// } -// } else if (lwm2mClientRpcRequest != null) { -// String errorMsg = String.format("Path %s not found in object version", targetIdVer); -// handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); -// } -// } else { -// switch (typeOper) { -// case OBSERVE_READ_ALL: -// case DISCOVER_ALL: -// Set paths; -// if (OBSERVE_READ_ALL.name().equals(typeOper.name())) { -// Set observations = context.getServer().getObservationService().getObservations(registration); -// paths = observations.stream().map(observation -> observation.getPath().toString()).collect(Collectors.toUnmodifiableSet()); -// } else { -// assert registration != null; -// Link[] objectLinks = registration.getSortedObjectLinks(); -// paths = Arrays.stream(objectLinks).map(Link::toString).collect(Collectors.toUnmodifiableSet()); -// } -// String msg = String.format("%s: type operation %s paths - %s", LOG_LW2M_INFO, -// typeOper.name(), paths); -// this.handler.sendLogsToThingsboard(client, msg); -// if (lwm2mClientRpcRequest != null) { -// String valueMsg = String.format("Paths - %s", paths); -// handler.sentRpcResponse(lwm2mClientRpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE); -// } -// break; -// case OBSERVE_CANCEL: -// case OBSERVE_CANCEL_ALL: -// int observeCancelCnt = 0; -// String observeCancelMsg = null; -// if (OBSERVE_CANCEL.name().equals(typeOper)) { -// observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration, target); -// observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LW2M_INFO, -// OBSERVE_CANCEL.name(), target, observeCancelCnt); -// } else { -// observeCancelCnt = context.getServer().getObservationService().cancelObservations(registration); -// observeCancelMsg = String.format("%s: type operation %s paths: All count: %d", LOG_LW2M_INFO, -// OBSERVE_CANCEL.name(), observeCancelCnt); -// } -// this.afterObserveCancel(client, observeCancelCnt, observeCancelMsg, lwm2mClientRpcRequest); -// break; -// // lwm2mClientRpcRequest != null -// case FW_UPDATE: -// handler.getInfoFirmwareUpdate(client, lwm2mClientRpcRequest); -// break; -// } -// } -// } catch (Exception e) { -// String msg = String.format("%s: type operation %s %s", LOG_LW2M_ERROR, -// typeOper.name(), e.getMessage()); -// handler.sendLogsToThingsboard(client, msg); -// if (lwm2mClientRpcRequest != null) { -// String errorMsg = String.format("Path %s type operation %s %s", targetIdVer, typeOper.name(), e.getMessage()); -// handler.sentRpcResponse(lwm2mClientRpcRequest, NOT_FOUND.getName(), errorMsg, LOG_LW2M_ERROR); -// } -// } -// } - - private void sendRequest(LwM2mClient client, SimpleDownlinkRequest request, long timeoutInMs, DownlinkRequestCallback callback) { + private , T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback callback) { Registration registration = client.getRegistration(); context.getServer().send(registration, request, timeoutInMs, response -> { -// if (!client.isInit()) { -// client.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); -// } responseRequestExecutor.submit(() -> { try { - callback.onSuccess(response); + callback.onSuccess(request, response); } catch (Exception e) { log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, e); } }); -// if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { -// this.handleResponse(client, request.getPath().toString(), response, request, rpcRequest); -// } else { -// 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()); -// handler.sendLogsToThingsboard(client, msg); -// log.error("[{}] [{}], [{}] - [{}] [{}] error SendRequest", request.getClass().getName().toString(), registration.getEndpoint(), -// ((Response) response.getCoapResponse()).getCode(), response.getCode(), request.getPath().toString()); -// if (!client.isInit()) { -// client.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); -// } -// /** Not Found */ -// if (rpcRequest != null) { -// handler.sentRpcResponse(rpcRequest, response.getCode().getName(), response.getErrorMessage(), LOG_LW2M_ERROR); -// } -// /** Not Found -// set setClient_fw_info... = empty -// **/ -// if (client.getFwUpdate() != null && client.getFwUpdate().isInfoFwSwUpdate()) { -// client.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); -// } -// if (client.getSwUpdate() != null && client.getSwUpdate().isInfoFwSwUpdate()) { -// client.getSwUpdate().initReadValue(handler, this, request.getPath().toString()); -// } -// if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { -// this.afterWriteFwSWUpdateError(registration, request, response.getErrorMessage()); -// } -// if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) { -// this.afterExecuteFwSwUpdateError(registration, request, response.getErrorMessage()); -// } -// } }, e -> { responseRequestExecutor.submit(() -> { - callback.onError(e); + callback.onError(JacksonUtil.toString(request), e); }); -// /** version == null -// set setClient_fw_info... = empty -// **/ -// if (client.getFwUpdate() != null && client.getFwUpdate().isInfoFwSwUpdate()) { -// client.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); -// } -// if (client.getSwUpdate() != null && client.getSwUpdate().isInfoFwSwUpdate()) { -// client.getSwUpdate().initReadValue(handler, this, request.getPath().toString()); -// } -// if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { -// this.afterWriteFwSWUpdateError(registration, request, e.getMessage()); -// } -// if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) { -// this.afterExecuteFwSwUpdateError(registration, request, e.getMessage()); -// } -// if (!client.isInit()) { -// client.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); -// } -// 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()); -// handler.sendLogsToThingsboard(client, msg); -// log.error("[{}] [{}] - [{}] error SendRequest", request.getClass().getName().toString(), request.getPath().toString(), e.toString()); -// if (rpcRequest != null) { -// handler.sentRpcResponse(rpcRequest, CoAP.CodeClass.ERROR_RESPONSE.name(), e.getMessage(), LOG_LW2M_ERROR); -// } }); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java index 342abf39f4..eea4e7ac2b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java @@ -15,28 +15,29 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -public interface DownlinkRequestCallback { +public interface DownlinkRequestCallback { - void onSuccess(T response); + void onSuccess(R request, T response); - void onValidationError(String msg); + void onValidationError(String params, String msg); - void onError(Exception e); + void onError(String params, Exception e); + + static DownlinkRequestCallback doNothing() { + return new DownlinkRequestCallback<>() { - static DownlinkRequestCallback doNothing() { - return new DownlinkRequestCallback() { @Override - public void onSuccess(T response) { + public void onSuccess(R request, T response) { } @Override - public void onValidationError(String msg) { + public void onValidationError(String params, String msg) { } @Override - public void onError(Exception e) { + public void onError(String params, Exception e) { } }; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java index 724dc78993..adb2e32293 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java @@ -16,6 +16,13 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.request.DeleteRequest; +import org.eclipse.leshan.core.request.DiscoverRequest; +import org.eclipse.leshan.core.request.ExecuteRequest; +import org.eclipse.leshan.core.request.ObserveRequest; +import org.eclipse.leshan.core.request.ReadRequest; +import org.eclipse.leshan.core.request.WriteAttributesRequest; +import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -30,29 +37,29 @@ import java.util.Set; public interface LwM2mDownlinkMsgHandler { - void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback); + void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback); - void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback); + void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback); - void sendObserveAllRequest(LwM2mClient client, TbLwM2MObserveAllRequest request, DownlinkRequestCallback> callback); + void sendObserveAllRequest(LwM2mClient client, TbLwM2MObserveAllRequest request, DownlinkRequestCallback> callback); - void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback); + void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback); - void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback callback); + void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback callback); - void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback); + void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback); - void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback callback); + void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback callback); - void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback); + void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback); - void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); + void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); - void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback); + void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback); - void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback); + void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback); - void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback); + void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java index 6d58853164..42933689ae 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java @@ -18,18 +18,18 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL_ALL; -public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback { public TbLwM2MCancelAllObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) { super(handler, client); } @Override - public void onSuccess(Integer canceledSubscriptionsCount) { - String observeCancelMsg = String.format("%s: type operation %s paths: count: %d", LOG_LW2M_INFO, OBSERVE_CANCEL_ALL.name(), canceledSubscriptionsCount); + public void onSuccess(TbLwM2MCancelAllRequest request, Integer canceledSubscriptionsCount) { + String observeCancelMsg = String.format("%s: type operation %s paths: count: %d", LOG_LWM2M_INFO, OBSERVE_CANCEL_ALL.name(), canceledSubscriptionsCount); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java index d0fc5fdd6a..65179de20b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java @@ -18,10 +18,10 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL; -public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback { private final String versionedId; @@ -31,8 +31,8 @@ public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback } @Override - public void onSuccess(Integer canceledSubscriptionsCount) { - String observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LW2M_INFO, OBSERVE_CANCEL.name(), versionedId, canceledSubscriptionsCount); + public void onSuccess(TbLwM2MCancelObserveRequest request, Integer canceledSubscriptionsCount) { + String observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LWM2M_INFO, OBSERVE_CANCEL.name(), versionedId, canceledSubscriptionsCount); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java index 8bd2e7505f..32c82404c6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java @@ -15,22 +15,15 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.response.DeleteResponse; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -public class TbLwM2MDeleteCallback extends AbstractTbLwM2MRequestCallback { - - private final String targetId; +public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback { public TbLwM2MDeleteCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client); - this.targetId = targetId; - } - - @Override - public void onSuccess(DeleteResponse response) { - //TODO: separate callback wrapper for the RPC calls. + super(handler, client, targetId); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java index e21949f7c4..5ae20e064d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java @@ -15,22 +15,15 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.response.DiscoverResponse; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MDiscoverCallback extends AbstractTbLwM2MRequestCallback { - - private final String targetId; +public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback { public TbLwM2MDiscoverCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client); - this.targetId = targetId; - } - - @Override - public void onSuccess(DiscoverResponse response) { - //TODO: separate callback wrapper for the RPC calls. + super(handler, client, targetId); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java index ff7c40b3b9..5d01e4267e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java @@ -15,11 +15,12 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.response.ExecuteResponse; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MExecuteCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MExecuteCallback extends AbstractTbLwM2MRequestCallback { private final String targetId; @@ -29,7 +30,7 @@ public class TbLwM2MExecuteCallback extends AbstractTbLwM2MRequestCallback { +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; - private final String targetId; +@Slf4j +public class TbLwM2MObserveCallback extends TbLwM2MTargetedCallback { public TbLwM2MObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client); - this.targetId = targetId; + super(handler, client, targetId); } @Override - public void onSuccess(ObserveResponse response) { - //TODO: handle the response (at least log to telemetry) + public void onSuccess(ObserveRequest request, ObserveResponse response) { + super.onSuccess(request, response); + handler.onUpdateValueAfterReadResponse(client.getRegistration(), targetId, response, null); } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java index 9ab30a9410..b6d0801f40 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java @@ -15,22 +15,24 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MReadCallback extends AbstractTbLwM2MRequestCallback { +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; - private final String targetId; +@Slf4j +public class TbLwM2MReadCallback extends TbLwM2MTargetedCallback { public TbLwM2MReadCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client); - this.targetId = targetId; + super(handler, client, targetId); } @Override - public void onSuccess(ReadResponse response) { - //TODO: separate callback wrapper for the RPC calls. + public void onSuccess(ReadRequest request, ReadResponse response) { + super.onSuccess(request, response); handler.onUpdateValueAfterReadResponse(client.getRegistration(), targetId, response, null); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java new file mode 100644 index 0000000000..e331abec11 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.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.transport.lwm2m.server.downlink; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.request.ReadRequest; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; + +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; + +@Slf4j +public abstract class TbLwM2MTargetedCallback extends AbstractTbLwM2MRequestCallback { + + protected final String targetId; + + public TbLwM2MTargetedCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + super(handler, client); + this.targetId = targetId; + } + + @Override + public void onSuccess(R request, T response) { + //TODO convert camelCase to "camel case" using .split("(? { +public class TbLwM2MWriteAttributesCallback extends AbstractTbLwM2MRequestCallback { private final String targetId; @@ -29,7 +30,7 @@ public class TbLwM2MWriteAttributesCallback extends AbstractTbLwM2MRequestCallba } @Override - public void onSuccess(WriteAttributesResponse response) { + public void onSuccess(WriteAttributesRequest request, WriteAttributesResponse response) { //TODO: separate callback wrapper for the RPC calls. } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java index 2cee4887c9..b383239777 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import lombok.Setter; +import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MWriteResponseCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MWriteResponseCallback extends AbstractTbLwM2MRequestCallback { private final String targetId; @@ -29,7 +31,8 @@ public class TbLwM2MWriteResponseCallback extends AbstractTbLwM2MRequestCallback } @Override - public void onSuccess(WriteResponse response) { + public void onSuccess(WriteRequest request, WriteResponse response) { + handler.onWriteResponseOk(client, targetId, request); //TODO: separate callback wrapper for the RPC calls. } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 0c371dac4e..80dad717aa 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -59,9 +59,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; -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.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_VALUE; @Slf4j @Service @@ -167,12 +167,12 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendObserveAllRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { TbLwM2MObserveAllRequest request = TbLwM2MObserveAllRequest.builder().timeout(this.config.getTimeout()).build(); - downlinkHandler.sendObserveAllRequest(client, request, new RpcLinkSetCallback(transportService, client, requestMsg, null)); + downlinkHandler.sendObserveAllRequest(client, request, new RpcLinkSetCallback<>(transportService, client, requestMsg, null)); } private void sendDiscoverAllRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { TbLwM2MDiscoverAllRequest request = TbLwM2MDiscoverAllRequest.builder().timeout(this.config.getTimeout()).build(); - downlinkHandler.sendDiscoverAllRequest(client, request, new RpcLinkSetCallback(transportService, client, requestMsg, null)); + downlinkHandler.sendDiscoverAllRequest(client, request, new RpcLinkSetCallback<>(transportService, client, requestMsg, null)); } private void sendDiscoverRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { @@ -235,7 +235,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendCancelAllObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { TbLwM2MCancelAllRequest downlink = TbLwM2MCancelAllRequest.builder().timeout(this.config.getTimeout()).build(); var mainCallback = new TbLwM2MCancelAllObserveCallback(uplinkHandler, client); - var rpcCallback = new RpcCancelObserveCallback(transportService, client, requestMsg, mainCallback); + var rpcCallback = new RpcCancelAllObserveCallback(transportService, client, requestMsg, mainCallback); downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback); } @@ -283,18 +283,18 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { public void sentRpcResponse(LwM2mClientRpcRequest rpcRequest, String requestCode, String msg, String typeMsg) { rpcRequest.setResponseCode(requestCode); - if (LOG_LW2M_ERROR.equals(typeMsg)) { + if (LOG_LWM2M_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)) { + } else if (LOG_LWM2M_INFO.equals(typeMsg)) { if (rpcRequest.getInfoMsg() == null) { rpcRequest.setInfoMsg(msg); } - } else if (LOG_LW2M_VALUE.equals(typeMsg)) { + } else if (LOG_LWM2M_VALUE.equals(typeMsg)) { if (rpcRequest.getValueMsg() == null) { rpcRequest.setValueMsg(msg); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.java new file mode 100644 index 0000000000..d2b7ffb834 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelAllObserveCallback.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.transport.lwm2m.server.rpc; + +import org.eclipse.leshan.core.ResponseCode; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; + +public class RpcCancelAllObserveCallback extends RpcDownlinkRequestCallbackProxy { + + public RpcCancelAllObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + super(transportService, client, requestMsg, callback); + } + + @Override + protected void sendRpcReplyOnSuccess(Integer response) { + reply(LwM2MRpcResponseBody.builder().result(ResponseCode.CONTENT.getName()).value(response.toString()).build()); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java index 8fafa294ee..1d1f1de230 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcCancelObserveCallback.java @@ -25,12 +25,13 @@ import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; import java.util.Optional; -public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy { +public class RpcCancelObserveCallback extends RpcDownlinkRequestCallbackProxy { - public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + public RpcCancelObserveCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { super(transportService, client, requestMsg, callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java index 54c621d8f9..ba8a634de0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDiscoverCallback.java @@ -19,6 +19,7 @@ import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.jetbrains.annotations.NotNull; @@ -30,9 +31,9 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import java.util.Optional; -public class RpcDiscoverCallback extends RpcLwM2MDownlinkCallback { +public class RpcDiscoverCallback extends RpcLwM2MDownlinkCallback { - public RpcDiscoverCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + public RpcDiscoverCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { super(transportService, client, requestMsg, callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java index 050e265b34..115680b50f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcDownlinkRequestCallbackProxy.java @@ -24,16 +24,16 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; -public abstract class RpcDownlinkRequestCallbackProxy implements DownlinkRequestCallback { +public abstract class RpcDownlinkRequestCallbackProxy implements DownlinkRequestCallback { private final TransportService transportService; private final TransportProtos.ToDeviceRpcRequestMsg request; - private final DownlinkRequestCallback callback; + private final DownlinkRequestCallback callback; protected final LwM2mClient client; protected final LwM2mValueConverter converter; - public RpcDownlinkRequestCallbackProxy(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + public RpcDownlinkRequestCallbackProxy(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { this.transportService = transportService; this.client = client; this.request = requestMsg; @@ -42,26 +42,26 @@ public abstract class RpcDownlinkRequestCallbackProxy implements DownlinkRequ } @Override - public void onSuccess(T response) { + public void onSuccess(R request, T response) { sendRpcReplyOnSuccess(response); if (callback != null) { - callback.onSuccess(response); + callback.onSuccess(request, response); } } @Override - public void onValidationError(String msg) { + public void onValidationError(String params, String msg) { sendRpcReplyOnValidationError(msg); if (callback != null) { - callback.onValidationError(msg); + callback.onValidationError(params, msg); } } @Override - public void onError(Exception e) { + public void onError(String params, Exception e) { sendRpcReplyOnError(e); if (callback != null) { - callback.onError(e); + callback.onError(params, e); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcEmptyResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcEmptyResponseCallback.java index a5f075b258..5bb2a8550f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcEmptyResponseCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcEmptyResponseCallback.java @@ -15,8 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.rpc; -import org.eclipse.leshan.core.Link; -import org.eclipse.leshan.core.response.DiscoverResponse; +import org.eclipse.leshan.core.request.LwM2mRequest; import org.eclipse.leshan.core.response.LwM2mResponse; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; @@ -25,9 +24,9 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import java.util.Optional; -public class RpcEmptyResponseCallback extends RpcLwM2MDownlinkCallback { +public class RpcEmptyResponseCallback, T extends LwM2mResponse> extends RpcLwM2MDownlinkCallback { - public RpcEmptyResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + public RpcEmptyResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { super(transportService, client, requestMsg, callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java index bcb5b2ead5..82d880c206 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLinkSetCallback.java @@ -27,9 +27,9 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import java.util.Optional; import java.util.Set; -public class RpcLinkSetCallback extends RpcDownlinkRequestCallbackProxy { +public class RpcLinkSetCallback extends RpcDownlinkRequestCallbackProxy { - public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + public RpcLinkSetCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { super(transportService, client, requestMsg, callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLwM2MDownlinkCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLwM2MDownlinkCallback.java index c8d32ef184..70536d1be8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLwM2MDownlinkCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcLwM2MDownlinkCallback.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.rpc; +import org.eclipse.leshan.core.request.LwM2mRequest; import org.eclipse.leshan.core.response.LwM2mResponse; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.transport.TransportService; @@ -24,9 +25,9 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import java.util.Optional; -public abstract class RpcLwM2MDownlinkCallback extends RpcDownlinkRequestCallbackProxy { +public abstract class RpcLwM2MDownlinkCallback, T extends LwM2mResponse> extends RpcDownlinkRequestCallbackProxy { - public RpcLwM2MDownlinkCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { + public RpcLwM2MDownlinkCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { super(transportService, client, requestMsg, callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java index 6a4ed06d6b..d3da6e6a3f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/RpcReadResponseCallback.java @@ -18,6 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server.rpc; import org.eclipse.leshan.core.node.LwM2mObject; import org.eclipse.leshan.core.node.LwM2mObjectInstance; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.request.LwM2mRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; @@ -26,11 +27,11 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import java.util.Optional; -public class RpcReadResponseCallback extends RpcLwM2MDownlinkCallback { +public class RpcReadResponseCallback, T extends ReadResponse> extends RpcLwM2MDownlinkCallback { private final String versionedId; - public RpcReadResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId, DownlinkRequestCallback callback) { + public RpcReadResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId, DownlinkRequestCallback callback) { super(transportService, client, requestMsg, callback); this.versionedId = versionedId; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 5187c4c493..15df144cc4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -111,10 +111,10 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.D import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_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_TELEMETRY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_WARN; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; @@ -196,7 +196,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); if (lwM2MClient != null) { this.clientContext.register(lwM2MClient, registration); - this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_INFO + ": Client registered with registration id: " + registration.getId()); + this.logToTelemetry(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId()); SessionInfoProto sessionInfo = lwM2MClient.getSession(); transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, rpcHandler, sessionInfo)); log.warn("40) sessionId [{}] Registering rpc subscription after Registration client", new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); @@ -219,11 +219,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { // Race condition detected and the client was in progress of unregistration while new registration arrived. Let's try again. onRegistered(registration, previousObservations); } else { - this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState()); + this.logToTelemetry(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState()); } } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable registration.", registration.getEndpoint(), t); - this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_WARN + ": Client registration failed due to: " + t.getMessage()); + this.logToTelemetry(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to: " + t.getMessage()); } }); } @@ -255,7 +255,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t); - this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_ERROR + String.format(": Client update Registration, %s", t.getMessage())); + this.logToTelemetry(lwM2MClient, LOG_LWM2M_ERROR + String.format(": Client update Registration, %s", t.getMessage())); } }); } @@ -268,7 +268,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { unRegistrationExecutor.submit(() -> { LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); try { - this.sendLogsToThingsboard(client, LOG_LW2M_INFO + ": Client unRegistration"); + this.logToTelemetry(client, LOG_LWM2M_INFO + ": Client unRegistration"); clientContext.unregister(client, registration); SessionInfoProto sessionInfo = client.getSession(); if (sessionInfo != null) { @@ -283,7 +283,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { log.info("[{}] delete registration: [{}] {}.", registration.getEndpoint(), stateException.getState(), stateException.getMessage()); } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); - this.sendLogsToThingsboard(client, LOG_LW2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage())); + this.logToTelemetry(client, LOG_LWM2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage())); } }); } @@ -291,7 +291,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { @Override public void onSleepingDev(Registration registration) { log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); - this.sendLogsToThingsboard(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LW2M_INFO + ": Client is sleeping!"); + this.logToTelemetry(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is sleeping!"); //TODO: associate endpointId with device information. } @@ -323,13 +323,13 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (objectModelVersion != null) { if (response.getContent() instanceof LwM2mObject) { LwM2mObject lwM2mObject = (LwM2mObject) response.getContent(); - this.updateObjectResourceValue(registration, lwM2mObject, path); + this.updateObjectResourceValue(lwM2MClient, lwM2mObject, path); } else if (response.getContent() instanceof LwM2mObjectInstance) { LwM2mObjectInstance lwM2mObjectInstance = (LwM2mObjectInstance) response.getContent(); - this.updateObjectInstanceResourceValue(registration, lwM2mObjectInstance, path); + this.updateObjectInstanceResourceValue(lwM2MClient, lwM2mObjectInstance, path); } else if (response.getContent() instanceof LwM2mResource) { LwM2mResource lwM2mResource = (LwM2mResource) response.getContent(); - this.updateResourcesValue(registration, lwM2mResource, path); + this.updateResourcesValue(lwM2MClient, lwM2mResource, path); } } } @@ -374,14 +374,14 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } else { 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(lwM2MClient, logMsg); + LOG_LWM2M_ERROR, pathIdVer, valueNew); + this.logToTelemetry(lwM2MClient, logMsg); } } else if (!isFwSwWords(pathName)) { 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(lwM2MClient, logMsg); + LOG_LWM2M_ERROR, pathName, valueNew); + this.logToTelemetry(lwM2MClient, logMsg); } }); @@ -454,7 +454,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { @Override public void onAwakeDev(Registration registration) { log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); - this.sendLogsToThingsboard(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LW2M_INFO + ": Client is awake!"); + this.logToTelemetry(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is awake!"); //TODO: associate endpointId with device information. } @@ -463,17 +463,17 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param registrationId - Id of Registration LwM2M Client */ @Override - public void sendLogsToThingsboard(String registrationId, String logMsg) { - sendLogsToThingsboard(clientContext.getClientByRegistrationId(registrationId), logMsg); + public void logToTelemetry(String registrationId, String logMsg) { + logToTelemetry(clientContext.getClientByRegistrationId(registrationId), logMsg); } @Override - public void sendLogsToThingsboard(LwM2mClient client, String logMsg) { + public void logToTelemetry(LwM2mClient client, String logMsg) { if (logMsg != null && client != null && client.getSession() != null) { if (logMsg.length() > 1024) { logMsg = logMsg.substring(0, 1024); } - this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LW2M_TELEMETRY, logMsg), client.getSession()); + this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), client.getSession()); } } @@ -563,29 +563,19 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveCallback(this, client, versionedId)); } - /** - * @param registration - - * @param lwM2mObject - - * @param pathIdVer - - */ - private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String pathIdVer) { + private void updateObjectResourceValue(LwM2mClient client, LwM2mObject lwM2mObject, String pathIdVer) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObject.getInstances().forEach((instanceId, instance) -> { String pathInstance = pathIds.toString() + "/" + instanceId; - this.updateObjectInstanceResourceValue(registration, instance, pathInstance); + this.updateObjectInstanceResourceValue(client, instance, pathInstance); }); } - /** - * @param registration - - * @param lwM2mObjectInstance - - * @param pathIdVer - - */ - private void updateObjectInstanceResourceValue(Registration registration, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer) { + private void updateObjectInstanceResourceValue(LwM2mClient client, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer) { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { String pathRez = pathIds.toString() + "/" + resourceId; - this.updateResourcesValue(registration, resource, pathRez); + this.updateResourcesValue(client, resource, pathRez); }); } @@ -596,12 +586,12 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * #3 If fr_update -> UpdateFirmware * #4 updateAttrTelemetry * - * @param registration - Registration LwM2M Client + * @param lwM2MClient - Registration LwM2M Client * @param lwM2mResource - LwM2mSingleResource response.getContent() * @param path - resource */ - private void updateResourcesValue(Registration registration, LwM2mResource lwM2mResource, String path) { - LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); + private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path) { + Registration registration = lwM2MClient.getRegistration(); if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { /** version != null * set setClient_fw_info... = value @@ -801,12 +791,13 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param path - * @param request - */ - public void onWriteResponseOk(Registration registration, String path, WriteRequest request) { + @Override + public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) { if (request.getNode() instanceof LwM2mResource) { - this.updateResourcesValue(registration, ((LwM2mResource) request.getNode()), path); + this.updateResourcesValue(client, ((LwM2mResource) request.getNode()), path); } else if (request.getNode() instanceof LwM2mObjectInstance) { ((LwM2mObjectInstance) request.getNode()).getResources().forEach((resId, resource) -> { - this.updateResourcesValue(registration, resource, path + "/" + resId); + this.updateResourcesValue(client, resource, path + "/" + resId); }); } @@ -945,8 +936,8 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } else { log.error("Failed update resource [{}] [{}]", versionedId, newValue); String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", - LOG_LW2M_ERROR, versionedId, newValue); - this.sendLogsToThingsboard(lwM2MClient, logMsg); + LOG_LWM2M_ERROR, versionedId, newValue); + this.logToTelemetry(lwM2MClient, logMsg); log.info("Failed update resource [{}] [{}]", versionedId, newValue); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java index 5d273abde7..f29deb1cf3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.uplink; import org.eclipse.leshan.core.observation.Observation; +import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.Device; @@ -54,9 +55,11 @@ public interface LwM2mUplinkMsgHandler { void onAwakeDev(Registration registration); - void sendLogsToThingsboard(LwM2mClient client, String msg); + void logToTelemetry(LwM2mClient client, String msg); - void sendLogsToThingsboard(String registrationId, String msg); + void logToTelemetry(String registrationId, String msg); + + void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request); void onToTransportUpdateCredentials(TransportProtos.ToTransportUpdateCredentialsProto updateCredentials); From 2c31de4a2c3ae2f53c45eecff5abcb848a56e596 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 15 Jun 2021 14:49:45 +0300 Subject: [PATCH 375/476] Fix duplication of sessions --- .../server/client/LwM2mClientContext.java | 4 +-- .../server/client/LwM2mClientContextImpl.java | 5 +++- .../TbLwM2MCancelAllObserveCallback.java | 8 ++++-- .../TbLwM2MCancelObserveCallback.java | 5 +++- .../downlink/TbLwM2MExecuteCallback.java | 12 ++------ .../downlink/TbLwM2MObserveCallback.java | 6 +--- .../server/downlink/TbLwM2MReadCallback.java | 4 +-- .../downlink/TbLwM2MTargetedCallback.java | 12 ++++---- .../TbLwM2MWriteAttributesCallback.java | 14 ++-------- .../TbLwM2MWriteResponseCallback.java | 14 ++++------ .../uplink/DefaultLwM2MUplinkMsgHandler.java | 28 +++++++++---------- 11 files changed, 46 insertions(+), 66 deletions(-) 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 f0539a0ccc..0f2f208b2b 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 @@ -18,11 +18,11 @@ package org.thingsboard.server.transport.lwm2m.server.client; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import java.util.Collection; +import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -34,7 +34,7 @@ public interface LwM2mClientContext { LwM2mClient getClientBySessionInfo(TransportProtos.SessionInfoProto sessionInfo); - void register(LwM2mClient lwM2MClient, Registration registration) throws LwM2MClientStateException; + Optional register(LwM2mClient lwM2MClient, Registration registration) throws LwM2MClientStateException; void updateRegistration(LwM2mClient client, Registration registration) throws LwM2MClientStateException; 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 c063a6f359..b9206ca4df 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 @@ -60,12 +60,14 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } @Override - public void register(LwM2mClient lwM2MClient, Registration registration) throws LwM2MClientStateException { + public Optional register(LwM2mClient lwM2MClient, Registration registration) throws LwM2MClientStateException { + TransportProtos.SessionInfoProto oldSession = null; lwM2MClient.lock(); try { if (LwM2MClientState.UNREGISTERED.equals(lwM2MClient.getState())) { throw new LwM2MClientStateException(lwM2MClient.getState(), "Client is in invalid state."); } + oldSession = lwM2MClient.getSession(); TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint()); if (securityInfo.getSecurityMode() != null) { if (securityInfo.getDeviceProfile() != null) { @@ -89,6 +91,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } finally { lwM2MClient.unlock(); } + return Optional.ofNullable(oldSession); } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java index 42933689ae..42f2a1baec 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java @@ -15,12 +15,13 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL_ALL; +@Slf4j public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback { public TbLwM2MCancelAllObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) { @@ -29,7 +30,8 @@ public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallb @Override public void onSuccess(TbLwM2MCancelAllRequest request, Integer canceledSubscriptionsCount) { - String observeCancelMsg = String.format("%s: type operation %s paths: count: %d", LOG_LWM2M_INFO, OBSERVE_CANCEL_ALL.name(), canceledSubscriptionsCount); + log.trace("[{}] Cancel of all observations was successful: {}", client.getEndpoint(), canceledSubscriptionsCount); + handler.logToTelemetry(client, String.format("[%s]: Cancel of all observations was successful. Result: [%s]", LOG_LWM2M_INFO, canceledSubscriptionsCount)); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java index 65179de20b..eec33963d5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java @@ -15,12 +15,14 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL; +@Slf4j public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback { private final String versionedId; @@ -32,7 +34,8 @@ public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback @Override public void onSuccess(TbLwM2MCancelObserveRequest request, Integer canceledSubscriptionsCount) { - String observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LWM2M_INFO, OBSERVE_CANCEL.name(), versionedId, canceledSubscriptionsCount); + log.trace("[{}] Cancel observation of [{}] successful: {}", client.getEndpoint(), versionedId, canceledSubscriptionsCount); + handler.logToTelemetry(client, String.format("[%s]: Cancel Observe for [%s] successful. Result: [%s]", LOG_LWM2M_INFO, versionedId, canceledSubscriptionsCount)); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java index 5d01e4267e..d2aed6a4fd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java @@ -20,18 +20,10 @@ import org.eclipse.leshan.core.response.ExecuteResponse; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MExecuteCallback extends AbstractTbLwM2MRequestCallback { - - private final String targetId; +public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback { public TbLwM2MExecuteCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client); - this.targetId = targetId; - } - - @Override - public void onSuccess(ExecuteRequest request, ExecuteResponse response) { - //TODO: separate callback wrapper for the RPC calls. + super(handler, client, targetId); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java index 890a6e6097..e5453902fb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java @@ -17,14 +17,10 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.request.ObserveRequest; -import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; - @Slf4j public class TbLwM2MObserveCallback extends TbLwM2MTargetedCallback { @@ -35,6 +31,6 @@ public class TbLwM2MObserveCallback extends TbLwM2MTargetedCallback { @@ -33,7 +31,7 @@ public class TbLwM2MReadCallback extends TbLwM2MTargetedCallback extends AbstractTbLwM2MRequestCallback { - protected final String targetId; + protected final String versionedId; - public TbLwM2MTargetedCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + public TbLwM2MTargetedCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String versionedId) { super(handler, client); - this.targetId = targetId; + this.versionedId = versionedId; } @Override public void onSuccess(R request, T response) { //TODO convert camelCase to "camel case" using .split("(? { - - private final String targetId; +public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback { public TbLwM2MWriteAttributesCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client); - this.targetId = targetId; - } - - @Override - public void onSuccess(WriteAttributesRequest request, WriteAttributesResponse response) { - //TODO: separate callback wrapper for the RPC calls. + super(handler, client, targetId); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java index b383239777..89586bd113 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java @@ -15,25 +15,21 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import lombok.Setter; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.WriteResponse; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -public class TbLwM2MWriteResponseCallback extends AbstractTbLwM2MRequestCallback { - - private final String targetId; +public class TbLwM2MWriteResponseCallback extends TbLwM2MTargetedCallback { public TbLwM2MWriteResponseCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client); - this.targetId = targetId; + super(handler, client, targetId); } @Override public void onSuccess(WriteRequest request, WriteResponse response) { - handler.onWriteResponseOk(client, targetId, request); - //TODO: separate callback wrapper for the RPC calls. + super.onSuccess(request, response); + handler.onWriteResponseOk(client, versionedId, request); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 15df144cc4..34163815d7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -195,7 +195,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { try { log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); if (lwM2MClient != null) { - this.clientContext.register(lwM2MClient, registration); + Optional oldSessionInfo = this.clientContext.register(lwM2MClient, registration); + if (oldSessionInfo.isPresent()) { + log.info("[{}] Closing old session: {}", registration.getEndpoint(), new UUID(oldSessionInfo.get().getSessionIdMSB(), oldSessionInfo.get().getSessionIdLSB())); + closeSession(oldSessionInfo.get()); + } this.logToTelemetry(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId()); SessionInfoProto sessionInfo = lwM2MClient.getSession(); transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, rpcHandler, sessionInfo)); @@ -272,8 +276,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { clientContext.unregister(client, registration); SessionInfoProto sessionInfo = client.getSession(); if (sessionInfo != null) { - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); - transportService.deregisterSession(sessionInfo); + closeSession(sessionInfo); sessionStore.remove(registration.getEndpoint()); log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); } else { @@ -288,6 +291,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { }); } + public void closeSession(SessionInfoProto sessionInfo) { + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); + transportService.deregisterSession(sessionInfo); + } + @Override public void onSleepingDev(Registration registration) { log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); @@ -441,8 +449,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { */ @Override public void doDisconnect(SessionInfoProto sessionInfo) { - transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); - transportService.deregisterSession(sessionInfo); + closeSession(sessionInfo); } /** @@ -586,7 +593,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * #3 If fr_update -> UpdateFirmware * #4 updateAttrTelemetry * - * @param lwM2MClient - Registration LwM2M Client + * @param lwM2MClient - Registration LwM2M Client * @param lwM2mResource - LwM2mSingleResource response.getContent() * @param path - resource */ @@ -784,13 +791,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { return lwm2mResourceValue; } - /** - * Update resource (attribute) value on thingsboard after update value in client - * - * @param registration - - * @param path - - * @param request - - */ @Override public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) { if (request.getNode() instanceof LwM2mResource) { @@ -955,7 +955,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * Get path to resource from profile equal keyName * * @param sessionInfo - - * @param keyName - + * @param keyName - * @return - */ @Override From 2d4c793b9f6566357acdc1a0a5a4b8435bcb63f2 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 15 Jun 2021 15:25:40 +0300 Subject: [PATCH 376/476] LwM2M refactoring form structure --- .../lwm2m-device-config-server.component.ts | 30 +++-- ...ile-transport-configuration.component.html | 69 +++++------ ...ofile-transport-configuration.component.ts | 116 +++++++++--------- .../lwm2m/lwm2m-profile-config.models.ts | 24 ++-- .../assets/locale/locale.constant-en_US.json | 18 ++- 5 files changed, 135 insertions(+), 122 deletions(-) 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 cccaed895f..f7c37c8109 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 @@ -15,7 +15,14 @@ /// import { Component, forwardRef, Input, OnDestroy, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, ValidationErrors, Validator, + Validators +} from '@angular/forms'; import { DEFAULT_PORT_BOOTSTRAP_NO_SEC, DEFAULT_PORT_SERVER_NO_SEC, @@ -40,11 +47,16 @@ import { deepClone } from '@core/utils'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => Lwm2mDeviceConfigServerComponent), multi: true - } + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => Lwm2mDeviceConfigServerComponent), + multi: true + }, ] }) -export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAccessor, OnDestroy { +export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAccessor, Validator, OnDestroy { private disabled = false; private destroy$ = new Subject(); @@ -159,11 +171,7 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc private propagateChangeState = (value: ServerSecurityConfig): void => { if (value !== undefined) { - if (this.serverFormGroup.valid) { - this.propagateChange(value); - } else { - this.propagateChange(null); - } + this.propagateChange(value); } } @@ -198,4 +206,10 @@ export class Lwm2mDeviceConfigServerComponent implements OnInit, ControlValueAcc } return config; } + + validate(): ValidationErrors | null { + return this.serverFormGroup.valid ? null : { + serverFormGroup: true + }; + } } 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 30b78393d3..13e32f10b5 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 @@ -34,53 +34,47 @@ - -
+
+
{{ 'device-profile.lwm2m.servers' | translate }} - +
{{ 'device-profile.lwm2m.short-id' | translate }} - - - {{ 'device-profile.lwm2m.short-id' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} + + + {{ 'device-profile.lwm2m.short-id-required' | translate }} {{ 'device-profile.lwm2m.lifetime' | translate }} - - - {{ 'device-profile.lwm2m.lifetime' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} + + + {{ 'device-profile.lwm2m.lifetime-required' | translate }} {{ 'device-profile.lwm2m.default-min-period' | translate }} - - - {{ 'device-profile.lwm2m.default-min-period' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} + + + {{ 'device-profile.lwm2m.default-min-period-required' | translate }}
{{ 'device-profile.lwm2m.binding' | translate }} - - {{ bindingModeTypeNamesMap.get(bindingModeType[bindingMode]) }} + + {{ bindingModeTypeNamesMap.get(bindingMode) | translate }} - {{ 'device-profile.lwm2m.notif-if-disabled' | translate }} + {{ 'device-profile.lwm2m.notification-storing' | translate }}
@@ -88,33 +82,27 @@ {{ 'device-profile.lwm2m.bootstrap-server' | translate }} - - - - + + {{ 'device-profile.lwm2m.lwm2m-server' | translate }} - - - - + +
- +
- -
+ +
device-profile.lwm2m.fw-update @@ -128,7 +116,7 @@ {{ 'device-profile.lwm2m.fw-update-recourse' | translate }} - + {{ 'device-profile.lwm2m.fw-update-recourse-required' | translate }} @@ -145,7 +133,7 @@ {{ 'device-profile.lwm2m.sw-update-recourse' | translate }} - + {{ 'device-profile.lwm2m.sw-update-recourse-required' | translate }} @@ -182,6 +170,7 @@
object; isFwUpdateStrategy: boolean; isSwUpdateStrategy: boolean; @@ -90,45 +91,53 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.lwm2mDeviceProfileFormGroup = this.fb.group({ objectIds: [null, Validators.required], observeAttrTelemetry: [null, Validators.required], - shortId: [null, Validators.required], - lifetime: [null, Validators.required], - defaultMinPeriod: [null, Validators.required], - notifIfDisabled: [true, []], - binding: [], - bootstrapServer: [null, Validators.required], - lwm2mServer: [null, Validators.required], - clientStrategy: [1, []], - fwUpdateStrategy: [1, []], - swUpdateStrategy: [1, []], - fwUpdateRecourse: [{value: '', disabled: true}, []], - swUpdateRecourse: [{value: '', disabled: true}, []] + bootstrap: this.fb.group({ + servers: this.fb.group({ + binding: [DEFAULT_BINDING], + shortId: [DEFAULT_ID_SERVER, [Validators.required, Validators.min(0)]], + lifetime: [DEFAULT_LIFE_TIME, [Validators.required, Validators.min(0)]], + notifIfDisabled: [DEFAULT_NOTIF_IF_DESIBLED, []], + defaultMinPeriod: [DEFAULT_MIN_PERIOD, [Validators.required, Validators.min(0)]], + }), + bootstrapServer: [null, Validators.required], + lwm2mServer: [null, Validators.required] + }), + clientLwM2mSettings: this.fb.group({ + clientStrategy: [1, []], + fwUpdateStrategy: [1, []], + swUpdateStrategy: [1, []], + fwUpdateRecourse: [{value: '', disabled: true}, []], + swUpdateRecourse: [{value: '', disabled: true}, []] + }) }); this.lwm2mDeviceConfigFormGroup = this.fb.group({ configurationJson: [null, Validators.required] }); - this.lwm2mDeviceProfileFormGroup.get('fwUpdateStrategy').valueChanges.pipe( + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateStrategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((fwStrategy) => { if (fwStrategy === 2) { - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').enable({emitEvent: false}); - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').patchValue(DEFAULT_FW_UPDATE_RESOURCE, {emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').enable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse') + .patchValue(DEFAULT_FW_UPDATE_RESOURCE, {emitEvent: false}); this.isFwUpdateStrategy = true; } else { - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').disable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').disable({emitEvent: false}); this.isFwUpdateStrategy = false; } this.otaUpdateFwStrategyValidate(true); }); - this.lwm2mDeviceProfileFormGroup.get('swUpdateStrategy').valueChanges.pipe( + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateStrategy').valueChanges.pipe( takeUntil(this.destroy$) ).subscribe((swStrategy) => { if (swStrategy === 2) { - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').enable({emitEvent: false}); - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').patchValue(DEFAULT_SW_UPDATE_RESOURCE, {emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').enable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse') + .patchValue(DEFAULT_SW_UPDATE_RESOURCE, {emitEvent: false}); this.isSwUpdateStrategy = true; } else { this.isSwUpdateStrategy = false; - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').disable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').disable({emitEvent: false}); } this.otaUpdateSwStrategyValidate(true); }); @@ -209,18 +218,14 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro this.lwm2mDeviceProfileFormGroup.patchValue({ objectIds: value, observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value.objectsList), - shortId: this.configurationValue.bootstrap.servers.shortId, - lifetime: this.configurationValue.bootstrap.servers.lifetime, - defaultMinPeriod: this.configurationValue.bootstrap.servers.defaultMinPeriod, - notifIfDisabled: this.configurationValue.bootstrap.servers.notifIfDisabled, - binding: this.configurationValue.bootstrap.servers.binding, - bootstrapServer: this.configurationValue.bootstrap.bootstrapServer, - lwm2mServer: this.configurationValue.bootstrap.lwm2mServer, - clientStrategy: this.configurationValue.clientLwM2mSettings.clientStrategy, - fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, - swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1, - fwUpdateRecourse: fwResource, - swUpdateRecourse: swResource + bootstrap: this.configurationValue.bootstrap, + clientLwM2mSettings: { + clientStrategy: this.configurationValue.clientLwM2mSettings.clientStrategy, + fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, + swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1, + fwUpdateRecourse: fwResource, + swUpdateRecourse: swResource + } }, {emitEvent: false}); this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = fwResource; @@ -249,21 +254,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private updateDeviceProfileValue(config): void { if (this.lwm2mDeviceProfileFormGroup.valid) { this.updateObserveAttrTelemetryFromGroupToJson(config.observeAttrTelemetry.clientLwM2M); - this.configurationValue.bootstrap.bootstrapServer = config.bootstrapServer; - this.configurationValue.bootstrap.lwm2mServer = config.lwm2mServer; - const bootstrapServers = this.configurationValue.bootstrap.servers; - bootstrapServers.shortId = config.shortId; - bootstrapServers.lifetime = config.lifetime; - bootstrapServers.defaultMinPeriod = config.defaultMinPeriod; - bootstrapServers.notifIfDisabled = config.notifIfDisabled; - bootstrapServers.binding = config.binding; - this.configurationValue.clientLwM2mSettings.clientStrategy = config.clientStrategy; - this.configurationValue.clientLwM2mSettings.fwUpdateStrategy = config.fwUpdateStrategy; - this.configurationValue.clientLwM2mSettings.swUpdateStrategy = config.swUpdateStrategy; - this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = config.fwUpdateRecourse; - this.configurationValue.clientLwM2mSettings.swUpdateRecourse = config.swUpdateRecourse; - this.upDateJsonAllConfig(); } + this.configurationValue.bootstrap.bootstrapServer = config.bootstrap.bootstrapServer; + this.configurationValue.bootstrap.lwm2mServer = config.bootstrap.lwm2mServer; + this.configurationValue.bootstrap.servers = config.bootstrap.servers; + this.configurationValue.clientLwM2mSettings = config.clientLwM2mSettings; + this.upDateJsonAllConfig(); this.updateModel(); } @@ -539,20 +535,20 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private otaUpdateFwStrategyValidate(updated = false): void { if (this.isFwUpdateStrategy) { - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').setValidators([Validators.required]); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').setValidators([Validators.required]); } else { - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').clearValidators(); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').clearValidators(); } - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').updateValueAndValidity({emitEvent: updated}); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.fwUpdateRecourse').updateValueAndValidity({emitEvent: updated}); } private otaUpdateSwStrategyValidate(updated = false): void { if (this.isSwUpdateStrategy) { - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').setValidators([Validators.required]); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').setValidators([Validators.required]); } else { - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').clearValidators(); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').clearValidators(); } - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').updateValueAndValidity({emitEvent: updated}); + this.lwm2mDeviceProfileFormGroup.get('clientLwM2mSettings.swUpdateRecourse').updateValueAndValidity({emitEvent: updated}); } } 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 1f2d125d9f..a5d3502808 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 @@ -47,7 +47,7 @@ export const DEFAULT_FW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_ export const DEFAULT_SW_UPDATE_RESOURCE = DEFAULT_OTA_UPDATE_PROTOCOL + DEFAULT_LOCAL_HOST_NAME + ':' + DEFAULT_PORT_SERVER_NO_SEC; -export enum BINDING_MODE { +export enum BingingMode { U = 'U', UQ = 'UQ', T = 'T', @@ -60,18 +60,18 @@ export enum BINDING_MODE { TQS = 'TQS' } -export const BINDING_MODE_NAMES = new Map( +export const BingingModeTranslationsMap = 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'] + [BingingMode.U, 'device-profile.lwm2m.binding-type.u'], + [BingingMode.UQ, 'device-profile.lwm2m.binding-type.uq'], + [BingingMode.US, 'device-profile.lwm2m.binding-type.us'], + [BingingMode.UQS, 'device-profile.lwm2m.binding-type.uqs'], + [BingingMode.T, 'device-profile.lwm2m.binding-type.t'], + [BingingMode.TQ, 'device-profile.lwm2m.binding-type.tq'], + [BingingMode.TS, 'device-profile.lwm2m.binding-type.ts'], + [BingingMode.TQS, 'device-profile.lwm2m.binding-type.tqs'], + [BingingMode.S, 'device-profile.lwm2m.binding-type.s'], + [BingingMode.SQ, 'device-profile.lwm2m.binding-type.sq'] ] ); 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 ec084b8a3a..ddc3e83061 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1257,10 +1257,24 @@ "servers": "Servers", "short-id": "Short ID", "short-id-required": "Short ID is required.", - "lifetime": "Lifetime of the registration for this LwM2M client", + "lifetime": "LwM2M Client registration Lifetime", + "lifetime-required": "LwM2M Client registration Lifetime is required.", "default-min-period": "Minimum Period between two notifications (sec)", - "notif-if-disabled": "Notification Storing When Disabled or Offline", + "default-min-period-required": "Minimum Period is required.", + "notification-storing": "Notification storing when disabled or offline", "binding": "Binding", + "binding-type": { + "u": "U: UDP connection in standard mode", + "uq": "UQ: UDP connection in queue mode", + "us": "US: both UDP and SMS connections active, both in standard mode", + "uqs": "UQS: both UDP and SMS connections active; UDP in queue mode, SMS in standard mode", + "t": "T: TCP connection in standard mode", + "tq": "TQ: TCP connection in queue mode", + "ts": "TS: both TCP and SMS connections active, both in standard mode", + "tqs": "TQS: both TCP and SMS connections active; TCP in queue mode, SMS in standard mode", + "s": "S: SMS connection in standard mode", + "sq": "SQ: SMS connection in queue mode" + }, "bootstrap-tab": "Bootstrap", "bootstrap-server": "Bootstrap Server", "lwm2m-server": "LwM2M Server", From 51e7549b58428bef1540d80b715c687a66c4ec37 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 15 Jun 2021 16:22:23 +0300 Subject: [PATCH 377/476] Await execution for sendReadRequests, sendObserveRequests --- .../server/downlink/TbLwM2MLatchCallback.java | 45 +++++++++++++++++++ .../server/downlink/TbLwM2MReadCallback.java | 4 +- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 41 ++++++++++++++--- 3 files changed, 80 insertions(+), 10 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MLatchCallback.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MLatchCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MLatchCallback.java new file mode 100644 index 0000000000..a864275487 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MLatchCallback.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.transport.lwm2m.server.downlink; + +import lombok.RequiredArgsConstructor; + +import java.util.concurrent.CountDownLatch; + +@RequiredArgsConstructor +public class TbLwM2MLatchCallback implements DownlinkRequestCallback { + + private final CountDownLatch countDownLatch; + private final DownlinkRequestCallback callback; + + @Override + public void onSuccess(R request, T response) { + callback.onSuccess(request, response); + countDownLatch.countDown(); + } + + @Override + public void onValidationError(String params, String msg) { + callback.onValidationError(params, msg); + countDownLatch.countDown(); + } + + @Override + public void onError(String params, Exception e) { + callback.onError(params, e); + countDownLatch.countDown(); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java index b6d0801f40..46315a6fcb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java @@ -18,10 +18,8 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; - -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; @Slf4j public class TbLwM2MReadCallback extends TbLwM2MTargetedCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 15df144cc4..b1ffe66600 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -28,7 +28,10 @@ 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.observation.Observation; +import org.eclipse.leshan.core.request.ObserveRequest; +import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.WriteRequest; +import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; import org.springframework.context.annotation.Lazy; @@ -69,7 +72,9 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mFwSwUpdate; import org.thingsboard.server.transport.lwm2m.server.client.ParametersAnalyzeResult; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverCallback; @@ -99,6 +104,7 @@ import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -510,16 +516,29 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { Set targetIds = new HashSet<>(profile.getObserveAttr().getAttribute()); targetIds.addAll(profile.getObserveAttr().getTelemetry()); targetIds = targetIds.stream().filter(target -> isSupportedTargetId(supportedObjects, target)).collect(Collectors.toSet()); - lwM2MClient.getPendingReadRequests().addAll(targetIds); - targetIds.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId)); + + CountDownLatch latch = new CountDownLatch(targetIds.size()); + targetIds.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId, + new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, lwM2MClient, versionedId)))); + try { + latch.await(); + } catch (InterruptedException e) { + log.error("Failed to await Read requests!"); + } } private void sendObserveRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { Set targetIds = profile.getObserveAttr().getObserve(); targetIds = targetIds.stream().filter(target -> isSupportedTargetId(supportedObjects, target)).collect(Collectors.toSet()); -// TODO: why do we need to put observe into pending read requests? -// lwM2MClient.getPendingReadRequests().addAll(targetIds); - targetIds.forEach(targetId -> sendObserveRequest(lwM2MClient, targetId)); + + CountDownLatch latch = new CountDownLatch(targetIds.size()); + targetIds.forEach(targetId -> sendObserveRequest(lwM2MClient, targetId, + new TbLwM2MLatchCallback<>(latch, new TbLwM2MObserveCallback(this, lwM2MClient, targetId)))); + try { + latch.await(); + } catch (InterruptedException e) { + log.error("Failed to await Observe requests!"); + } } private void sendWriteAttributeRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { @@ -544,13 +563,21 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) { + sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, lwM2MClient, versionedId)); + } + + private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId, DownlinkRequestCallback callback) { TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, request, new TbLwM2MReadCallback(this, lwM2MClient, versionedId)); + defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, request, callback); } private void sendObserveRequest(LwM2mClient lwM2MClient, String versionedId) { + sendObserveRequest(lwM2MClient, versionedId, new TbLwM2MObserveCallback(this, lwM2MClient, versionedId)); + } + + private void sendObserveRequest(LwM2mClient lwM2MClient, String versionedId, DownlinkRequestCallback callback) { TbLwM2MObserveRequest request = TbLwM2MObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendObserveRequest(lwM2MClient, request, new TbLwM2MObserveCallback(this, lwM2MClient, versionedId)); + defaultLwM2MDownlinkMsgHandler.sendObserveRequest(lwM2MClient, request, callback); } private void sendWriteAttributesRequest(LwM2mClient lwM2MClient, String targetId, ObjectAttributes params) { From 807a543d1c66ff11da387baccc357cb0025e52af Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 15 Jun 2021 16:50:30 +0300 Subject: [PATCH 378/476] UI: Add validation ota package URL; Improvement ota-package-autocomplete; Refactoring update ota-packages in device-component --- .../home/pages/device/device.component.ts | 18 +++++++++++++----- .../pages/ota-update/ota-update.component.html | 2 +- .../pages/ota-update/ota-update.component.ts | 7 ++++++- .../ota-package-autocomplete.component.html | 3 ++- .../ota-package-autocomplete.component.ts | 2 +- 5 files changed, 23 insertions(+), 9 deletions(-) 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 1ed9f784f1..b55498ed05 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 @@ -35,6 +35,7 @@ import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { Subject } from 'rxjs'; import { OtaUpdateType } from '@shared/models/ota-package.models'; +import { distinctUntilChanged } from 'rxjs/operators'; @Component({ selector: 'tb-device', @@ -78,7 +79,7 @@ export class DeviceComponent extends EntityComponent { } buildForm(entity: DeviceInfo): FormGroup { - return this.fb.group( + const form = this.fb.group( { name: [entity ? entity.name : '', [Validators.required]], deviceProfileId: [entity ? entity.deviceProfileId : null, [Validators.required]], @@ -95,6 +96,17 @@ export class DeviceComponent extends EntityComponent { ) } ); + form.get('deviceProfileId').valueChanges.pipe( + distinctUntilChanged((prev, curr) => prev?.id === curr?.id) + ).subscribe(profileId => { + if (profileId && this.isEdit) { + this.entityForm.patchValue({ + firmwareId: null, + softwareId: null + }, {emitEvent: false}); + } + }); + return form; } updateForm(entity: DeviceInfo) { @@ -156,10 +168,6 @@ 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/ota-update/ota-update.component.html b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html index 0f2662a251..6802768303 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.html @@ -149,7 +149,7 @@ - + ota-update.direct-url-required diff --git a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts index 1182e6c40a..0c2d82e171 100644 --- a/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts +++ b/ui-ngx/src/app/modules/home/pages/ota-update/ota-update.component.ts @@ -67,7 +67,7 @@ export class OtaUpdateComponent extends EntityComponent implements O this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); } else { this.entityForm.get('file').clearValidators(); - this.entityForm.get('url').setValidators(Validators.required); + this.entityForm.get('url').setValidators([Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]); this.entityForm.get('file').updateValueAndValidity({emitEvent: false}); this.entityForm.get('url').updateValueAndValidity({emitEvent: false}); } @@ -172,6 +172,11 @@ export class OtaUpdateComponent extends EntityComponent implements O } prepareFormValue(formValue: any): any { + if (formValue.resource === 'url') { + delete formValue.file; + } else { + delete formValue.url; + } delete formValue.resource; delete formValue.generateChecksum; return super.prepareFormValue(formValue); diff --git a/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html b/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html index 51c291006d..b29f32a1c6 100644 --- a/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/ota-package/ota-package-autocomplete.component.html @@ -21,7 +21,8 @@ formControlName="packageId" (focusin)="onFocus()" [required]="required" - [matAutocomplete]="packageAutocomplete"> + [matAutocomplete]="packageAutocomplete" + [matAutocompleteDisabled]="disabled">
@@ -143,13 +143,25 @@ - {{ 'device-profile.lwm2m.sw-update-recourse' | translate }} - - - {{ 'device-profile.lwm2m.sw-update-recourse-required' | translate }} + {{ 'device-profile.lwm2m.sw-update-resource' | translate }} + + + {{ 'device-profile.lwm2m.sw-update-resource-required' | translate }} +
+ device-profile.lwm2m.power-mode + + + + + {{ powerModeLwM2MNamesMap.get(powerModeLwM2MType[powerMode]) }} + + + +
@@ -160,9 +172,9 @@ - + - + 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 faf94eccce..389a214d22 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 @@ -32,7 +32,7 @@ import { ModelValue, ObjectLwM2M, OBSERVE, - OBSERVE_ATTR_TELEMETRY, + OBSERVE_ATTR_TELEMETRY, powerMode, powerModeNames, RESOURCES, TELEMETRY } from './lwm2m-profile-config.models'; @@ -72,6 +72,9 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro sortFunction: (key: string, value: object) => object; isFwUpdateStrategy: boolean; isSwUpdateStrategy: boolean; + powerModeLwM2MType = powerMode; + powerModeLwM2MTypes = Object.keys(powerMode); + powerModeLwM2MNamesMap = powerModeNames; get required(): boolean { return this.requiredValue; @@ -97,11 +100,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro binding: [], bootstrapServer: [null, Validators.required], lwm2mServer: [null, Validators.required], - clientStrategy: [1, []], + clientOnlyObserveAfterConnect: [1, []], fwUpdateStrategy: [1, []], swUpdateStrategy: [1, []], - fwUpdateRecourse: [{value: '', disabled: true}, []], - swUpdateRecourse: [{value: '', disabled: true}, []] + fwUpdateResource: [{value: '', disabled: true}, []], + swUpdateResource: [{value: '', disabled: true}, []], + powerMode: [null, Validators.required] }); this.lwm2mDeviceConfigFormGroup = this.fb.group({ configurationJson: [null, Validators.required] @@ -110,11 +114,11 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro takeUntil(this.destroy$) ).subscribe((fwStrategy) => { if (fwStrategy === 2) { - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').enable({emitEvent: false}); - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').patchValue(DEFAULT_FW_UPDATE_RESOURCE, {emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('fwUpdateResource').enable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('fwUpdateResource').patchValue(DEFAULT_FW_UPDATE_RESOURCE, {emitEvent: false}); this.isFwUpdateStrategy = true; } else { - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').disable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('fwUpdateResource').disable({emitEvent: false}); this.isFwUpdateStrategy = false; } this.otaUpdateFwStrategyValidate(true); @@ -123,12 +127,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro takeUntil(this.destroy$) ).subscribe((swStrategy) => { if (swStrategy === 2) { - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').enable({emitEvent: false}); - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').patchValue(DEFAULT_SW_UPDATE_RESOURCE, {emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('swUpdateResource').enable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('swUpdateResource').patchValue(DEFAULT_SW_UPDATE_RESOURCE, {emitEvent: false}); this.isSwUpdateStrategy = true; } else { this.isSwUpdateStrategy = false; - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').disable({emitEvent: false}); + this.lwm2mDeviceProfileFormGroup.get('swUpdateResource').disable({emitEvent: false}); } this.otaUpdateSwStrategyValidate(true); }); @@ -202,10 +206,10 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private updateWriteValue = (value: ModelValue): void => { - const fwResource = isDefinedAndNotNull(this.configurationValue.clientLwM2mSettings.fwUpdateRecourse) ? - this.configurationValue.clientLwM2mSettings.fwUpdateRecourse : ''; - const swResource = isDefinedAndNotNull(this.configurationValue.clientLwM2mSettings.fwUpdateRecourse) ? - this.configurationValue.clientLwM2mSettings.swUpdateRecourse : ''; + const fwResource = isDefinedAndNotNull(this.configurationValue.clientLwM2mSettings.fwUpdateResource) ? + this.configurationValue.clientLwM2mSettings.fwUpdateResource : ''; + const swResource = isDefinedAndNotNull(this.configurationValue.clientLwM2mSettings.fwUpdateResource) ? + this.configurationValue.clientLwM2mSettings.swUpdateResource : ''; this.lwm2mDeviceProfileFormGroup.patchValue({ objectIds: value, observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value.objectsList), @@ -216,15 +220,16 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro binding: this.configurationValue.bootstrap.servers.binding, bootstrapServer: this.configurationValue.bootstrap.bootstrapServer, lwm2mServer: this.configurationValue.bootstrap.lwm2mServer, - clientStrategy: this.configurationValue.clientLwM2mSettings.clientStrategy, + clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1, - fwUpdateRecourse: fwResource, - swUpdateRecourse: swResource + fwUpdateResource: fwResource, + swUpdateResource: swResource, + powerMode: this.configurationValue.clientLwM2mSettings.powerMode }, {emitEvent: false}); - this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = fwResource; - this.configurationValue.clientLwM2mSettings.swUpdateRecourse = swResource; + this.configurationValue.clientLwM2mSettings.fwUpdateResource = fwResource; + this.configurationValue.clientLwM2mSettings.swUpdateResource = swResource; this.isFwUpdateStrategy = this.configurationValue.clientLwM2mSettings.fwUpdateStrategy === 2; this.isSwUpdateStrategy = this.configurationValue.clientLwM2mSettings.swUpdateStrategy === 2; this.otaUpdateSwStrategyValidate(); @@ -257,11 +262,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro bootstrapServers.defaultMinPeriod = config.defaultMinPeriod; bootstrapServers.notifIfDisabled = config.notifIfDisabled; bootstrapServers.binding = config.binding; - this.configurationValue.clientLwM2mSettings.clientStrategy = config.clientStrategy; + this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect = config.clientOnlyObserveAfterConnect; this.configurationValue.clientLwM2mSettings.fwUpdateStrategy = config.fwUpdateStrategy; this.configurationValue.clientLwM2mSettings.swUpdateStrategy = config.swUpdateStrategy; - this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = config.fwUpdateRecourse; - this.configurationValue.clientLwM2mSettings.swUpdateRecourse = config.swUpdateRecourse; + this.configurationValue.clientLwM2mSettings.fwUpdateResource = config.fwUpdateResource; + this.configurationValue.clientLwM2mSettings.swUpdateResource = config.swUpdateResource; + this.configurationValue.clientLwM2mSettings.powerMode = config.powerMode; this.upDateJsonAllConfig(); } this.updateModel(); @@ -539,20 +545,20 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro private otaUpdateFwStrategyValidate(updated = false): void { if (this.isFwUpdateStrategy) { - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').setValidators([Validators.required]); + this.lwm2mDeviceProfileFormGroup.get('fwUpdateResource').setValidators([Validators.required]); } else { - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').clearValidators(); + this.lwm2mDeviceProfileFormGroup.get('fwUpdateResource').clearValidators(); } - this.lwm2mDeviceProfileFormGroup.get('fwUpdateRecourse').updateValueAndValidity({emitEvent: updated}); + this.lwm2mDeviceProfileFormGroup.get('fwUpdateResource').updateValueAndValidity({emitEvent: updated}); } private otaUpdateSwStrategyValidate(updated = false): void { if (this.isSwUpdateStrategy) { - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').setValidators([Validators.required]); + this.lwm2mDeviceProfileFormGroup.get('swUpdateResource').setValidators([Validators.required]); } else { - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').clearValidators(); + this.lwm2mDeviceProfileFormGroup.get('swUpdateResource').clearValidators(); } - this.lwm2mDeviceProfileFormGroup.get('swUpdateRecourse').updateValueAndValidity({emitEvent: updated}); + this.lwm2mDeviceProfileFormGroup.get('swUpdateResource').updateValueAndValidity({emitEvent: updated}); } } 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 1f2d125d9f..fa9cbd4083 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 @@ -128,6 +128,20 @@ export const securityConfigModeNames = new Map( ] ); +export enum powerMode { + PSM = 'PSM', + DRX = 'DRX', + E_DRX = 'E_DRX' +} + +export const powerModeNames = new Map( + [ + [powerMode.PSM, 'Power Saving Mode (PSM)'], + [powerMode.DRX, 'Discontinuous Reception (DRX)'], + [powerMode.E_DRX, 'Extended Discontinuous Reception (eDRX)'] + ] +); + export interface ModelValue { objectIds: string[]; objectsList: ObjectLwM2M[]; @@ -168,11 +182,12 @@ export interface Lwm2mProfileConfigModels { } export interface ClientLwM2mSettings { - clientStrategy: string; + clientOnlyObserveAfterConnect: string; fwUpdateStrategy: number; swUpdateStrategy: number; - fwUpdateRecourse: string; - swUpdateRecourse: string; + fwUpdateResource: string; + swUpdateResource: string; + powerMode: powerMode; } export interface ObservableAttributes { @@ -240,11 +255,12 @@ export function getDefaultProfileConfig(hostname?: any): Lwm2mProfileConfigModel function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { return { - clientStrategy: '1', + clientOnlyObserveAfterConnect: '1', fwUpdateStrategy: 1, swUpdateStrategy: 1, - fwUpdateRecourse: DEFAULT_FW_UPDATE_RESOURCE, - swUpdateRecourse: DEFAULT_SW_UPDATE_RESOURCE + fwUpdateResource: DEFAULT_FW_UPDATE_RESOURCE, + swUpdateResource: DEFAULT_SW_UPDATE_RESOURCE, + powerMode: powerMode.DRX }; } 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 cb09d84731..f50cec88cf 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1288,11 +1288,12 @@ "sw-update-strategy": "Software update strategy", "sw-update-strategy-package": "Push binary file using Object 9 and Resource 2 (Package)", "sw-update-strategy-package-uri": "Auto-generate unique CoAP URL to download the package and push software update using Object 9 and Resource 3 (Package URI)", - "fw-update-recourse": "Firmware update CoAP recourse", - "fw-update-recourse-required": "Firmware update CoAP recourse is required.", - "sw-update-recourse": "Software update CoAP recourse", - "sw-update-recourse-required": "Software update CoAP recourse is required.", - "config-json-tab": "Json Config Profile Device" + "fw-update-resource": "Firmware update CoAP resource", + "fw-update-resource-required": "Firmware update CoAP resource is required.", + "sw-update-resource": "Software update CoAP resource", + "sw-update-resource-required": "Software update CoAP resource is required.", + "config-json-tab": "Json Config Profile Device", + "power-mode": "Power Mode" } }, "dialog": { From 2a5ba8e8ad78b302f2174c444443badb22044dad Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 16 Jun 2021 18:46:26 +0300 Subject: [PATCH 384/476] Add Apple OAuth2 provider. --- .../oauth2_config_templates/apple_config.json | 24 +++++ .../main/data/upgrade/3.2.2/schema_update.sql | 2 +- .../auth/oauth2/AppleOAuth2ClientMapper.java | 101 ++++++++++++++++++ .../auth/oauth2/BasicOAuth2ClientMapper.java | 3 +- .../auth/oauth2/CustomOAuth2ClientMapper.java | 4 +- .../auth/oauth2/GithubOAuth2ClientMapper.java | 3 +- .../auth/oauth2/OAuth2ClientMapper.java | 4 +- .../oauth2/OAuth2ClientMapperProvider.java | 6 ++ .../Oauth2AuthenticationFailureHandler.java | 1 - .../Oauth2AuthenticationSuccessHandler.java | 2 +- .../server/common/data/oauth2/MapperType.java | 2 +- .../model/sql/OAuth2RegistrationEntity.java | 2 +- .../server/dao/oauth2/OAuth2ServiceImpl.java | 3 - .../resources/sql/schema-entities-hsql.sql | 2 +- .../main/resources/sql/schema-entities.sql | 2 +- ui-ngx/src/app/app.component.ts | 7 ++ .../admin/oauth2-settings.component.html | 5 +- .../pages/admin/oauth2-settings.component.ts | 3 +- ui-ngx/src/app/shared/models/oauth2.models.ts | 3 +- 19 files changed, 158 insertions(+), 21 deletions(-) create mode 100644 application/src/main/data/json/system/oauth2_config_templates/apple_config.json create mode 100644 application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java diff --git a/application/src/main/data/json/system/oauth2_config_templates/apple_config.json b/application/src/main/data/json/system/oauth2_config_templates/apple_config.json new file mode 100644 index 0000000000..a956920b6b --- /dev/null +++ b/application/src/main/data/json/system/oauth2_config_templates/apple_config.json @@ -0,0 +1,24 @@ +{ + "providerId": "Apple", + "additionalInfo": null, + "accessTokenUri": "https://appleid.apple.com/auth/token", + "authorizationUri": "https://appleid.apple.com/auth/authorize?response_mode=form_post", + "scope": ["email","openid","name"], + "jwkSetUri": "https://appleid.apple.com/auth/keys", + "userInfoUri": null, + "clientAuthenticationMethod": "POST", + "userNameAttributeName": "email", + "mapperConfig": { + "type": "APPLE", + "basic": { + "emailAttributeKey": "email", + "firstNameAttributeKey": "firstName", + "lastNameAttributeKey": "lastName", + "tenantNameStrategy": "DOMAIN" + } + }, + "comment": null, + "loginButtonIcon": "apple-logo", + "loginButtonLabel": "Apple", + "helpLink": "https://developer.apple.com/sign-in-with-apple/get-started/" +} 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 d57668de0d..8020046a7b 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 @@ -92,7 +92,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration ( created_time bigint NOT NULL, additional_info varchar, client_id varchar(255), - client_secret varchar(255), + client_secret varchar(2048), authorization_uri varchar(255), token_uri varchar(255), scope varchar(255), diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java new file mode 100644 index 0000000000..93da71169c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AppleOAuth2ClientMapper.java @@ -0,0 +1,101 @@ +/** + * 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.security.auth.oauth2; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; +import org.thingsboard.server.common.data.oauth2.OAuth2Registration; +import org.thingsboard.server.dao.oauth2.OAuth2User; +import org.thingsboard.server.service.security.model.SecurityUser; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +@Service(value = "appleOAuth2ClientMapper") +@Slf4j +public class AppleOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { + + private static final String USER = "user"; + private static final String NAME = "name"; + private static final String FIRST_NAME = "firstName"; + private static final String LAST_NAME = "lastName"; + private static final String EMAIL = "email"; + + @Override + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + OAuth2MapperConfig config = registration.getMapperConfig(); + Map attributes = updateAttributesFromRequestParams(request, token.getPrincipal().getAttributes()); + String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); + OAuth2User oauth2User = BasicMapperUtils.getOAuth2User(email, attributes, config); + + return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); + } + + private static Map updateAttributesFromRequestParams(HttpServletRequest request, Map attributes) { + Map updated = attributes; + MultiValueMap params = toMultiMap(request.getParameterMap()); + String userValue = params.getFirst(USER); + if (StringUtils.hasText(userValue)) { + JsonNode user = null; + try { + user = JacksonUtil.toJsonNode(userValue); + } catch (Exception e) {} + if (user != null) { + updated = new HashMap<>(attributes); + if (user.has(NAME)) { + JsonNode name = user.get(NAME); + if (name.isObject()) { + JsonNode firstName = name.get(FIRST_NAME); + if (firstName != null && firstName.isTextual()) { + updated.put(FIRST_NAME, firstName.asText()); + } + JsonNode lastName = name.get(LAST_NAME); + if (lastName != null && lastName.isTextual()) { + updated.put(LAST_NAME, lastName.asText()); + } + } + } + if (user.has(EMAIL)) { + JsonNode email = user.get(EMAIL); + if (email != null && email.isTextual()) { + updated.put(EMAIL, email.asText()); + } + } + } + } + return updated; + } + + private static MultiValueMap toMultiMap(Map map) { + MultiValueMap params = new LinkedMultiValueMap<>(map.size()); + map.forEach((key, values) -> { + if (values.length > 0) { + for (String value : values) { + params.add(key, value); + } + } + }); + return params; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java index f5172b64e7..d2532d0240 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; +import javax.servlet.http.HttpServletRequest; import java.util.Map; @Service(value = "basicOAuth2ClientMapper") @@ -30,7 +31,7 @@ import java.util.Map; public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { OAuth2MapperConfig config = registration.getMapperConfig(); Map attributes = token.getPrincipal().getAttributes(); String email = BasicMapperUtils.getStringAttributeByKey(attributes, config.getBasic().getEmailAttributeKey()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java index 65ebb384de..778f7416ff 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java @@ -29,6 +29,8 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; +import javax.servlet.http.HttpServletRequest; + @Service(value = "customOAuth2ClientMapper") @Slf4j public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper implements OAuth2ClientMapper { @@ -39,7 +41,7 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private RestTemplateBuilder restTemplateBuilder = new RestTemplateBuilder(); @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { OAuth2MapperConfig config = registration.getMapperConfig(); OAuth2User oauth2User = getOAuth2User(token, providerAccessToken, config.getCustom()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, registration); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java index d6260fe482..3810f36757 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/GithubOAuth2ClientMapper.java @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.service.security.model.SecurityUser; +import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Map; import java.util.Optional; @@ -46,7 +47,7 @@ public class GithubOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme private OAuth2Configuration oAuth2Configuration; @Override - public SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { + public SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration) { OAuth2MapperConfig config = registration.getMapperConfig(); Map githubMapperConfig = oAuth2Configuration.getGithubMapper(); String email = getEmail(githubMapperConfig.get(EMAIL_URL_KEY), providerAccessToken); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java index 280418d066..39957602d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapper.java @@ -20,6 +20,8 @@ import org.thingsboard.server.common.data.oauth2.OAuth2Registration; import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientRegistrationInfo; import org.thingsboard.server.service.security.model.SecurityUser; +import javax.servlet.http.HttpServletRequest; + public interface OAuth2ClientMapper { - SecurityUser getOrCreateUserByClientPrincipal(OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration); + SecurityUser getOrCreateUserByClientPrincipal(HttpServletRequest request, OAuth2AuthenticationToken token, String providerAccessToken, OAuth2Registration registration); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java index 606b4f9b1d..df9e5e05ad 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/OAuth2ClientMapperProvider.java @@ -37,6 +37,10 @@ public class OAuth2ClientMapperProvider { @Qualifier("githubOAuth2ClientMapper") private OAuth2ClientMapper githubOAuth2ClientMapper; + @Autowired + @Qualifier("appleOAuth2ClientMapper") + private OAuth2ClientMapper appleOAuth2ClientMapper; + public OAuth2ClientMapper getOAuth2ClientMapperByType(MapperType oauth2MapperType) { switch (oauth2MapperType) { case CUSTOM: @@ -45,6 +49,8 @@ public class OAuth2ClientMapperProvider { return basicOAuth2ClientMapper; case GITHUB: return githubOAuth2ClientMapper; + case APPLE: + return appleOAuth2ClientMapper; default: throw new RuntimeException("OAuth2ClientRegistrationMapper with type " + oauth2MapperType + " is not supported!"); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index 0e413b4f22..95b4643f4c 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -36,7 +36,6 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @Component(value = "oauth2AuthenticationFailureHandler") -@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { private final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 227d733ebd..303a430e77 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -90,7 +90,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS token.getAuthorizedClientRegistrationId(), token.getPrincipal().getName()); OAuth2ClientMapper mapper = oauth2ClientMapperProvider.getOAuth2ClientMapperByType(registration.getMapperConfig().getType()); - SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), + SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), registration); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java index 3f91e14bd9..4811e153d9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/oauth2/MapperType.java @@ -16,5 +16,5 @@ package org.thingsboard.server.common.data.oauth2; public enum MapperType { - BASIC, CUSTOM, GITHUB; + BASIC, CUSTOM, GITHUB, APPLE; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java index edcaa794a6..ca32bad322 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/OAuth2RegistrationEntity.java @@ -178,7 +178,7 @@ public class OAuth2RegistrationEntity extends BaseSqlEntity .activateUser(activateUser) .type(type) .basic( - (type == MapperType.BASIC || type == MapperType.GITHUB) ? + (type == MapperType.BASIC || type == MapperType.GITHUB || type == MapperType.APPLE) ? OAuth2BasicMapperConfig.builder() .emailAttributeKey(emailAttributeKey) .firstNameAttributeKey(firstNameAttributeKey) diff --git a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java index 83a134ea36..6d7dde1f21 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ServiceImpl.java @@ -377,9 +377,6 @@ public class OAuth2ServiceImpl extends AbstractEntityService implements OAuth2Se if (StringUtils.isEmpty(clientRegistration.getScope())) { throw new DataValidationException("Scope should be specified!"); } - if (StringUtils.isEmpty(clientRegistration.getUserInfoUri())) { - throw new DataValidationException("User info uri should be specified!"); - } if (StringUtils.isEmpty(clientRegistration.getUserNameAttributeName())) { throw new DataValidationException("User name attribute name should be specified!"); } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 16522becde..ca7cd73604 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -387,7 +387,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration ( created_time bigint NOT NULL, additional_info varchar, client_id varchar(255), - client_secret varchar(255), + client_secret varchar(2048), authorization_uri varchar(255), token_uri varchar(255), scope varchar(255), diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 88386a11dd..303bf6f0f9 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -424,7 +424,7 @@ CREATE TABLE IF NOT EXISTS oauth2_registration ( created_time bigint NOT NULL, additional_info varchar, client_id varchar(255), - client_secret varchar(255), + client_secret varchar(2048), authorization_uri varchar(255), token_uri varchar(255), scope varchar(255), diff --git a/ui-ngx/src/app/app.component.ts b/ui-ngx/src/app/app.component.ts index 3ad4038066..39d51ced92 100644 --- a/ui-ngx/src/app/app.component.ts +++ b/ui-ngx/src/app/app.component.ts @@ -89,6 +89,13 @@ export class AppComponent implements OnInit { ) ); + this.matIconRegistry.addSvgIconLiteral( + 'apple-logo', + this.domSanitizer.bypassSecurityTrustHtml( + '' + ) + ); + this.storageService.testLocalStorage(); this.setupTranslate(); diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html index a983266434..43989d907a 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.html @@ -321,16 +321,13 @@ admin.oauth2.user-info-uri - + - - {{ 'admin.oauth2.user-info-uri-required' | translate }} - {{ 'admin.oauth2.uri-pattern-error' | translate }} diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts index 4a123c642b..bf5dc75023 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2-settings.component.ts @@ -311,8 +311,7 @@ export class OAuth2SettingsComponent extends PageComponent implements OnInit, Ha scope: this.fb.array(registration?.scope ? registration.scope : [], OAuth2SettingsComponent.validateScope), jwkSetUri: [registration?.jwkSetUri ? registration.jwkSetUri : '', Validators.pattern(this.URL_REGEXP)], userInfoUri: [registration?.userInfoUri ? registration.userInfoUri : '', - [Validators.required, - Validators.pattern(this.URL_REGEXP)]], + [Validators.pattern(this.URL_REGEXP)]], clientAuthenticationMethod: [ registration?.clientAuthenticationMethod ? registration.clientAuthenticationMethod : ClientAuthenticationMethod.POST, Validators.required], diff --git a/ui-ngx/src/app/shared/models/oauth2.models.ts b/ui-ngx/src/app/shared/models/oauth2.models.ts index b434302a9a..3d2c125299 100644 --- a/ui-ngx/src/app/shared/models/oauth2.models.ts +++ b/ui-ngx/src/app/shared/models/oauth2.models.ts @@ -54,7 +54,8 @@ export const domainSchemaTranslations = new Map( export enum MapperConfigType{ BASIC = 'BASIC', CUSTOM = 'CUSTOM', - GITHUB = 'GITHUB' + GITHUB = 'GITHUB', + APPLE = 'APPLE' } export enum TenantNameStrategy{ From 9717ef5ff512d2d9148b65ebb6f7d00558ece4d5 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 16 Jun 2021 18:52:07 +0300 Subject: [PATCH 385/476] Fix DefaultTenantProfileConfiguration model --- ui-ngx/src/app/shared/models/tenant.model.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 4ce7acb5c5..badc48bea4 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -49,8 +49,10 @@ export interface DefaultTenantProfileConfiguration { maxRuleNodeExecutionsPerMessage: number; maxEmails: number; maxSms: number; + maxCreatedAlarms: number; defaultStorageTtlDays: number; + alarmsTtlDays: number; } export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; @@ -81,7 +83,9 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan maxRuleNodeExecutionsPerMessage: 0, maxEmails: 0, maxSms: 0, - defaultStorageTtlDays: 0 + maxCreatedAlarms: 0, + defaultStorageTtlDays: 0, + alarmsTtlDays: 0 }; configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; break; From 08939e23e76605a270972b0ed6046d9fb308d108 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 16 Jun 2021 19:00:45 +0300 Subject: [PATCH 386/476] Refactoring of the Firmware Update --- .../device/data/lwm2m/OtherConfiguration.java | 2 + .../server/LwM2MFirmwareUpdateStrategy.java | 48 ++++ .../server/LwM2MSoftwareUpdateStrategy.java | 48 ++++ .../lwm2m/server/LwM2mServerListener.java | 5 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 3 +- .../server/LwM2mTransportServerHelper.java | 6 +- .../lwm2m/server/LwM2mTransportUtil.java | 215 +++------------ .../lwm2m/server/UpdateResultFw.java | 70 +++++ .../transport/lwm2m/server/UpdateStateFw.java | 56 ++++ .../DefaultLwM2MAttributesService.java | 183 +++++++++---- .../attributes/LwM2MAttributesService.java | 4 +- .../lwm2m/server/client/LwM2mClient.java | 9 +- .../server/client/LwM2mClientContext.java | 5 +- .../server/client/LwM2mClientContextImpl.java | 41 ++- .../lwm2m/server/client/LwM2mFwSwUpdate.java | 65 ++--- .../downlink/TbLwM2MWriteReplaceRequest.java | 6 +- .../ota/DefaultLwM2MOtaUpdateService.java | 248 ++++++++++++++++-- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 99 +++++++ .../lwm2m/server/ota/LwM2MClientOtaState.java | 5 +- .../server/ota/LwM2MOtaUpdateService.java | 13 + .../rpc/DefaultLwM2MRpcRequestHandler.java | 2 +- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 159 ++--------- .../server/uplink/LwM2mUplinkMsgHandler.java | 4 - 23 files changed, 851 insertions(+), 445 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MFirmwareUpdateStrategy.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSoftwareUpdateStrategy.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateStateFw.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java index 2bfef74391..829180983b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java @@ -23,5 +23,7 @@ public class OtherConfiguration { private Integer fwUpdateStrategy; private Integer swUpdateStrategy; private Integer clientOnlyObserveAfterConnect; + private String fwUpdateRecourse; + private String swUpdateRecourse; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MFirmwareUpdateStrategy.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MFirmwareUpdateStrategy.java new file mode 100644 index 0000000000..f111152b76 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MFirmwareUpdateStrategy.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.transport.lwm2m.server; + +public enum LwM2MFirmwareUpdateStrategy { + OBJ_5_BINARY(1, "ObjectId 5, Binary"), + OBJ_5_TEMP_URL(2, "ObjectId 5, URI"), + OBJ_19_BINARY(3, "ObjectId 19, Binary"); + + public int code; + public String type; + + LwM2MFirmwareUpdateStrategy(int code, String type) { + this.code = code; + this.type = type; + } + + public static LwM2MFirmwareUpdateStrategy fromStrategyFwByType(String type) { + for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) { + if (to.type.equals(type)) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type)); + } + + public static LwM2MFirmwareUpdateStrategy fromStrategyFwByCode(int code) { + for (LwM2MFirmwareUpdateStrategy to : LwM2MFirmwareUpdateStrategy.values()) { + if (to.code == code) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported FW Strategy code : %s", code)); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSoftwareUpdateStrategy.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSoftwareUpdateStrategy.java new file mode 100644 index 0000000000..0df3f5ca2d --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSoftwareUpdateStrategy.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.transport.lwm2m.server; + +public enum LwM2MSoftwareUpdateStrategy { + BINARY(1, "ObjectId 9, Binary"), + TEMP_URL(2, "ObjectId 9, URI"); + + public int code; + public String type; + + LwM2MSoftwareUpdateStrategy(int code, String type) { + this.code = code; + this.type = type; + } + + public static LwM2MSoftwareUpdateStrategy fromStrategySwByType(String type) { + for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) { + if (to.type.equals(type)) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported SW Strategy type : %s", type)); + } + + public static LwM2MSoftwareUpdateStrategy fromStrategySwByCode(int code) { + for (LwM2MSoftwareUpdateStrategy to : LwM2MSoftwareUpdateStrategy.values()) { + if (to.code == code) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported SW Strategy code : %s", 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 36f1e8a903..1c2bb70145 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 @@ -27,8 +27,7 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandle import java.util.Collection; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; @Slf4j public class LwM2mServerListener { @@ -93,7 +92,7 @@ public class LwM2mServerListener { @Override public void onResponse(Observation observation, Registration registration, ObserveResponse response) { if (registration != null) { - service.onUpdateValueAfterReadResponse(registration, convertPathFromObjectIdToIdVer(observation.getPath().toString(), registration), response); + service.onUpdateValueAfterReadResponse(registration, convertObjectIdToVersionedId(observation.getPath().toString(), registration), response); } } 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 ebf0a48aba..f39ced6dfc 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 @@ -33,7 +33,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseM import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Optional; @@ -54,7 +53,7 @@ public class LwM2mSessionMsgListener implements GenericFutureListener>> futures; private final TransportService transportService; + private final LwM2mTransportServerHelper helper; + private final LwM2mClientContext clientContext; + private final LwM2MTransportServerConfig config; + private final LwM2mUplinkMsgHandler uplinkHandler; + private final LwM2mDownlinkMsgHandler downlinkHandler; + private final LwM2MOtaUpdateService otaUpdateService; @Override public ListenableFuture> getSharedAttributes(LwM2mClient client, Collection keys) { @@ -96,55 +116,114 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { * @param msg - */ @Override - public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { -// LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo); -// if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) { -// log.warn("2) OnAttributeUpdate, SharedUpdatedList() [{}]", msg.getSharedUpdatedList()); -// msg.getSharedUpdatedList().forEach(tsKvProto -> { -// String pathName = tsKvProto.getKv().getKey(); -// String pathIdVer = this.getObjectIdByKeyNameFromProfile(sessionInfo, pathName); -// Object valueNew = getValueFromKvProto(tsKvProto.getKv()); -// if ((OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) -// && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion()))) -// || (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName) -// && (!valueNew.equals(lwM2MClient.getFwUpdate().getCurrentTitle())))) { -// this.getInfoFirmwareUpdate(lwM2MClient, null); -// } else if ((OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName) -// && (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentVersion()))) -// || (OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName) -// && (!valueNew.equals(lwM2MClient.getSwUpdate().getCurrentTitle())))) { -// this.getInfoSoftwareUpdate(lwM2MClient, null); -// } -// if (pathIdVer != null) { -// ResourceModel resourceModel = lwM2MClient.getResourceModel(pathIdVer, this.config -// .getModelProvider()); -// if (resourceModel != null && resourceModel.operations.isWritable()) { -// this.updateResourcesValueToClient(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), valueNew, pathIdVer); -// } else { -// 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_LWM2M_ERROR, pathIdVer, valueNew); -// this.logToTelemetry(lwM2MClient, logMsg); -// } -// } else if (!isFwSwWords(pathName)) { -// 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_LWM2M_ERROR, pathName, valueNew); -// this.logToTelemetry(lwM2MClient, logMsg); -// } -// -// }); -// } else if (msg.getSharedDeletedCount() > 0 && lwM2MClient != null) { -// msg.getSharedUpdatedList().forEach(tsKvProto -> { -// String pathName = tsKvProto.getKv().getKey(); -// Object valueNew = getValueFromKvProto(tsKvProto.getKv()); -// if (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) { -// lwM2MClient.getFwUpdate().setCurrentVersion((String) valueNew); -// } -// }); -// log.info("[{}] delete [{}] onAttributeUpdate", msg.getSharedDeletedList(), sessionInfo); -// } else if (lwM2MClient == null) { -// log.error("OnAttributeUpdate, lwM2MClient is null"); -// } + public void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo) { + LwM2mClient lwM2MClient = clientContext.getClientBySessionInfo(sessionInfo); + if (msg.getSharedUpdatedCount() > 0 && lwM2MClient != null) { + String newFirmwareTitle = null; + String newFirmwareVersion = null; + String newFirmwareUrl = null; + String newSoftwareTitle = null; + String newSoftwareVersion = null; + List otherAttributes = new ArrayList<>(); + for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) { + String attrName = tsKvProto.getKv().getKey(); + if (DefaultLwM2MOtaUpdateService.FIRMWARE_TITLE.equals(attrName)) { + newFirmwareTitle = getStrValue(tsKvProto); + } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_VERSION.equals(attrName)) { + newFirmwareVersion = getStrValue(tsKvProto); + } else if (DefaultLwM2MOtaUpdateService.FIRMWARE_URL.equals(attrName)) { + newFirmwareUrl = getStrValue(tsKvProto); + } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_TITLE.equals(attrName)) { + newSoftwareTitle = getStrValue(tsKvProto); + } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) { + newSoftwareVersion = getStrValue(tsKvProto); + } else { + otherAttributes.add(tsKvProto); + } + } + if (newFirmwareTitle != null || newFirmwareVersion != null) { + otaUpdateService.onTargetFirmwareUpdate(lwM2MClient, newFirmwareTitle, newFirmwareVersion, Optional.ofNullable(newFirmwareUrl)); + } + if (newSoftwareTitle != null || newSoftwareVersion != null) { + otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion); + } + if (!otherAttributes.isEmpty()) { + onAttributesUpdate(lwM2MClient, otherAttributes); + } + } else if (msg.getSharedDeletedCount() > 0 && lwM2MClient != null) { + msg.getSharedUpdatedList().forEach(tsKvProto -> { + String pathName = tsKvProto.getKv().getKey(); + Object valueNew = getValueFromKvProto(tsKvProto.getKv()); + if (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) { + lwM2MClient.getFwUpdate().setCurrentVersion((String) valueNew); + } + }); + } else if (lwM2MClient == null) { + log.error("OnAttributeUpdate, lwM2MClient is null"); + } + } + + /** + * #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 + * + */ + @Override + public void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos) { + log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos); + tsKvProtos.forEach(tsKvProto -> { + String pathIdVer = clientContext.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey()); + if (pathIdVer != null) { + // #1.1 + if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { + if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { + lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); + } + } else { + lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); + } + } + }); + // #2.1 + lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> { + this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), + getValueFromKvProto(tsKvProto.getKv()), pathIdVer); + }); + } + + private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { + if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { + TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(this.config.getTimeout()).build(); + downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, lwM2MClient, versionedId)); + } else { + log.error("Failed update resource [{}] [{}]", versionedId, newValue); + String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", + LOG_LWM2M_ERROR, versionedId, newValue); + uplinkHandler.logToTelemetry(lwM2MClient, logMsg); + log.info("Failed update resource [{}] [{}]", versionedId, newValue); + } + } + + /** + * @param pathIdVer - path resource + * @return - value of Resource into format KvProto or null + */ + private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { + LwM2mResource resourceValue = LwM2mTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); + if (resourceValue != null) { + ResourceModel.Type currentType = resourceValue.getType(); + ResourceModel.Type expectedType = helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); + return LwM2mValueConverterImpl.getInstance().convertValue(resourceValue.getValue(), currentType, expectedType, + new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); + } else { + return null; + } + } + + private String getStrValue(TransportProtos.TsKvProto tsKvProto) { + return tsKvProto.getKv().getStringV(); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java index 008788cb33..b9d7fa9e21 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/LwM2MAttributesService.java @@ -28,5 +28,7 @@ public interface LwM2MAttributesService { void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg getAttributesResponse, TransportProtos.SessionInfoProto sessionInfo); - void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo); + void onAttributesUpdate(TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification, TransportProtos.SessionInfoProto sessionInfo); + + void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos); } 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 ec187549c8..21d6ec3d48 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 @@ -38,11 +38,9 @@ import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsRes import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Collection; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Queue; @@ -50,14 +48,13 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; 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.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId; @@ -202,7 +199,7 @@ public class LwM2mClient implements Cloneable { } public Object getResourceValue(String pathRezIdVer, String pathRezId) { - String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer; + String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer; if (this.resources.get(pathRez) != null) { return this.resources.get(pathRez).getLwM2mResource().getValue(); } @@ -210,7 +207,7 @@ public class LwM2mClient implements Cloneable { } public Object getResourceNameByRezId(String pathRezIdVer, String pathRezId) { - String pathRez = pathRezIdVer == null ? convertPathFromObjectIdToIdVer(pathRezId, this.registration) : pathRezIdVer; + String pathRez = pathRezIdVer == null ? convertObjectIdToVersionedId(pathRezId, this.registration) : pathRezIdVer; if (this.resources.get(pathRez) != null) { return this.resources.get(pathRez).getResourceModel().name; } 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 0f2f208b2b..006953af58 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 @@ -53,7 +53,10 @@ public interface LwM2mClientContext { LwM2mClient getClientByDeviceId(UUID deviceId); - void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); + String getObjectIdByKeyNameFromProfile(TransportProtos.SessionInfoProto sessionInfo, String keyName); + + String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName); + void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials); } 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 b9206ca4df..d60482a0c1 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; @@ -25,6 +26,7 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -40,7 +42,9 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.function.Predicate; import static org.eclipse.leshan.core.SecurityMode.NO_SEC; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey; @Slf4j @Service @@ -49,6 +53,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c public class LwM2mClientContextImpl implements LwM2mClientContext { private final LwM2mTransportContext context; + private final LwM2MTransportServerConfig config; private final TbEditableSecurityStore securityStore; private final Map lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); private final Map lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); @@ -160,6 +165,28 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { return lwM2mClient; } + /** + * Get path to resource from profile equal keyName + * + * @param sessionInfo - + * @param keyName - + * @return - + */ + @Override + public String getObjectIdByKeyNameFromProfile(TransportProtos.SessionInfoProto sessionInfo, String keyName) { + return getObjectIdByKeyNameFromProfile(getClientBySessionInfo(sessionInfo), keyName); + } + + @Override + public String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName) { + Lwm2mDeviceProfileTransportConfiguration profile = getProfile(lwM2mClient.getProfileId()); + + return profile.getObserveAttr().getKeyName().entrySet().stream() + .filter(e -> e.getValue().equals(keyName) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().orElseThrow( + () -> new IllegalArgumentException(keyName + " is not configured in the device profile!") + ).getKey(); + } + public Registration getRegistration(String registrationId) { return this.lwM2mClientsByRegistrationId.get(registrationId).getRegistration(); } @@ -200,7 +227,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { Arrays.stream(client.getRegistration().getObjectLinks()).forEach(link -> { LwM2mPath pathIds = new LwM2mPath(link.getUrl()); if (!pathIds.isRoot()) { - clientObjects.add(convertPathFromObjectIdToIdVer(link.getUrl(), client.getRegistration())); + clientObjects.add(convertObjectIdToVersionedId(link.getUrl(), client.getRegistration())); } }); return (clientObjects.size() > 0) ? clientObjects : null; @@ -211,4 +238,14 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { return lwM2mClientsByRegistrationId.values().stream().filter(e -> deviceId.equals(e.getDeviceId())).findFirst().orElse(null); } + private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { + ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config + .getModelProvider()); + Integer objectId = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)).getObjectId(); + String objectVer = validateObjectVerFromKey(pathIdVer); + return resourceModel != null && (isWritableNotOptional ? + objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() : + objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java index b19fa4ac55..b39f6084cd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java @@ -23,7 +23,10 @@ import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; +import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -62,9 +65,9 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.F import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; +import static org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.EXECUTE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.WRITE_REPLACE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID; @@ -75,8 +78,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.S import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsFwSateToFirmwareUpdateStatus; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.toOtaPackageUpdateStatus; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.splitCamelCaseString; @Slf4j @@ -140,8 +143,8 @@ public class LwM2mFwSwUpdate { private void initPathId() { if (FIRMWARE.equals(this.type)) { - this.pathPackageId = LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy ? - FW_PACKAGE_5_ID : LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy ? + this.pathPackageId = LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy ? + FW_PACKAGE_5_ID : LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy ? FW_PACKAGE_URI_ID : FW_PACKAGE_19_ID; this.pathStateId = FW_STATE_ID; this.pathResultId = FW_RESULT_ID; @@ -185,26 +188,26 @@ public class LwM2mFwSwUpdate { if (this.currentId != null) { this.stateUpdate = OtaPackageUpdateStatus.INITIATED.name(); this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LWM2M_INFO, null); - String targetIdVer = convertPathFromObjectIdToIdVer(this.pathPackageId, this.lwM2MClient.getRegistration()); + String targetIdVer = convertObjectIdToVersionedId(this.pathPackageId, this.lwM2MClient.getRegistration()); String fwMsg = String.format("%s: Start type operation %s paths: %s", LOG_LWM2M_INFO, LwM2mOperationType.FW_UPDATE.name(), this.pathPackageId); handler.logToTelemetry(fwMsg, lwM2MClient.getRegistration().getId()); log.warn("8) Start firmware Update. Send save to: [{}] ver: [{}] path: [{}]", this.lwM2MClient.getDeviceName(), this.currentVersion, targetIdVer); - if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy) { + if (LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy) { int chunkSize = 0; int chunk = 0; byte[] firmwareChunk = handler.otaPackageDataCache.get(this.currentId.toString(), chunkSize, chunk); TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(firmwareChunk).timeout(handler.config.getTimeout()).build(); request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteResponseCallback(handler, lwM2MClient, targetIdVer)); - } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) { + } else if (LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) { String apiFont = "coap://176.36.143.9:5685"; String uri = apiFont + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + this.currentId.toString(); log.warn("89) coapUri: [{}]", uri); //TODO: user this.rpcRequest??? TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(uri).timeout(handler.config.getTimeout()).build(); request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteResponseCallback(handler, lwM2MClient, targetIdVer)); - } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) { + } else if (LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) { } } else { @@ -269,7 +272,7 @@ public class LwM2mFwSwUpdate { (this.currentTitle != null && pathName != null && this.currentTitle.equals(pathName))) { fwMsg = String.format("%s: The update was interrupted. The device has the same version: %s.", LOG_LWM2M_ERROR, this.currentVersion); - } else if (updateResultFw != null && updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { + } else if (updateResultFw != null && updateResultFw > UpdateResultFw.UPDATE_SUCCESSFULLY.code) { fwMsg = String.format("%s: The update was interrupted. The device has the status UpdateResult: error (%d).", LOG_LWM2M_ERROR, updateResultFw); } @@ -288,7 +291,7 @@ public class LwM2mFwSwUpdate { */ public boolean conditionalFwExecuteStart() { Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return LwM2mTransportUtil.UpdateResultFw.INITIAL.code == updateResult; + return UpdateResultFw.INITIAL.code == updateResult; } /** @@ -297,7 +300,7 @@ public class LwM2mFwSwUpdate { */ public boolean conditionalFwExecuteAfterSuccess() { Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code == updateResult; + return UpdateResultFw.UPDATE_SUCCESSFULLY.code == updateResult; } /** @@ -306,7 +309,7 @@ public class LwM2mFwSwUpdate { */ public boolean conditionalFwExecuteAfterError() { Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code < updateResult; + return UpdateResultFw.UPDATE_SUCCESSFULLY.code < updateResult; } /** @@ -354,7 +357,7 @@ public class LwM2mFwSwUpdate { */ public void finishFwSwUpdate(DefaultLwM2MUplinkMsgHandler handler, boolean success) { Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - String value = FIRMWARE.equals(this.type) ? LwM2mTransportUtil.UpdateResultFw.fromUpdateResultFwByCode(updateResult.intValue()).type : + String value = FIRMWARE.equals(this.type) ? UpdateResultFw.fromUpdateResultFwByCode(updateResult.intValue()).type : LwM2mTransportUtil.UpdateResultSw.fromUpdateResultSwByCode(updateResult.intValue()).type; String key = splitCamelCaseString((String) this.lwM2MClient.getResourceNameByRezId(null, this.pathResultId)); if (success) { @@ -408,18 +411,18 @@ public class LwM2mFwSwUpdate { public void sendReadObserveInfo(LwM2mDownlinkMsgHandler request) { this.infoFwSwUpdate = true; - this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( + this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( this.pathStateId, this.lwM2MClient.getRegistration())); - this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( + this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( this.pathResultId, this.lwM2MClient.getRegistration())); - this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( + this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( FW_3_VER_ID, this.lwM2MClient.getRegistration())); - if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy || - LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy || + if (LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy || + LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy || SOFTWARE.equals(this.type)) { - this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( + this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( this.pathVerId, this.lwM2MClient.getRegistration())); - this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( + this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( this.pathNameId, this.lwM2MClient.getRegistration())); } this.pendingInfoRequestsStart.forEach(versionedId -> { @@ -442,7 +445,7 @@ public class LwM2mFwSwUpdate { public void updateStateOta(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request, Registration registration, String path, int value) { if (OBJ_5_BINARY.code == this.getUpdateStrategy()) { - if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { + if ((convertObjectIdToVersionedId(FW_RESULT_ID, registration).equals(path))) { if (DOWNLOADED.name().equals(this.getStateUpdate()) && this.conditionalFwExecuteStart()) { this.executeFwSwWare(handler, request); @@ -455,23 +458,23 @@ public class LwM2mFwSwUpdate { } } } else if (OBJ_5_TEMP_URL.code == this.getUpdateStrategy()) { - if (this.currentId != null && (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) { - String state = equalsFwSateToFirmwareUpdateStatus(LwM2mTransportUtil.StateFw.fromStateFwByCode(value)).name(); + if (this.currentId != null && (convertObjectIdToVersionedId(FW_STATE_ID, registration).equals(path))) { + String state = toOtaPackageUpdateStatus(UpdateStateFw.fromStateFwByCode(value)).name(); if (StringUtils.isNotEmpty(state) && !FAILED.name().equals(this.stateUpdate) && !state.equals(this.stateUpdate)) { this.stateUpdate = state; this.sendSateOnThingsBoard(handler); } - if (value == LwM2mTransportUtil.StateFw.DOWNLOADED.code) { + if (value == UpdateStateFw.DOWNLOADED.code) { this.executeFwSwWare(handler, request); } handler.firmwareUpdateState.put(lwM2MClient.getEndpoint(), value); } - if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { - if (this.currentId != null && value == LwM2mTransportUtil.UpdateResultFw.INITIAL.code) { + if ((convertObjectIdToVersionedId(FW_RESULT_ID, registration).equals(path))) { + if (this.currentId != null && value == UpdateResultFw.INITIAL.code) { this.setStateUpdate(INITIATED.name()); - } else if (this.currentId != null && value == LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { + } else if (this.currentId != null && value == UpdateResultFw.UPDATE_SUCCESSFULLY.code) { this.setStateUpdate(UPDATED.name()); - } else if (value > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { + } else if (value > UpdateResultFw.UPDATE_SUCCESSFULLY.code) { this.setStateUpdate(FAILED.name()); } this.sendSateOnThingsBoard(handler); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java index 007aa1d3f8..f4b1059944 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java @@ -17,17 +17,21 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; +import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + @Getter + private final ContentFormat contentFormat; @Getter private final Object value; @Builder - private TbLwM2MWriteReplaceRequest(String versionedId, long timeout, Object value) { + private TbLwM2MWriteReplaceRequest(String versionedId, long timeout, ContentFormat contentFormat, Object value) { super(versionedId, timeout); + this.contentFormat = contentFormat; this.value = value; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 0b862ff725..68d7d76b73 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -17,21 +17,50 @@ package org.thingsboard.server.transport.lwm2m.server.ota; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.request.ContentFormat; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageKey; import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; 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.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; 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.downlink.LwM2mDownlinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteUpdateRequest; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import javax.annotation.PostConstruct; import java.util.ArrayList; 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 static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; @Slf4j @Service @@ -39,45 +68,228 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttribute @RequiredArgsConstructor public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { + public static final String FIRMWARE_VERSION = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION); + public static final String FIRMWARE_TITLE = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE); + public static final String FIRMWARE_URL = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.URL); + public static final String SOFTWARE_VERSION = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION); + public static final String SOFTWARE_TITLE = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE); + public static final String SOFTWARE_URL = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.URL); + + private static final String FW_PACKAGE_5_ID = "/5/0/0"; + private static final String FW_URL_ID = "/5/0/1"; + private static final String FW_EXECUTE_ID = "/5/0/2"; private static final String FW_NAME_ID = "/5/0/6"; private static final String FW_VER_ID = "/5/0/7"; private static final String SW_NAME_ID = "/9/0/0"; private static final String SW_VER_ID = "/9/0/1"; - private final Map fwStates = new ConcurrentHashMap<>(); - private final Map swStates = new ConcurrentHashMap<>(); + private final Map fwStates = new ConcurrentHashMap<>(); + private final Map swStates = new ConcurrentHashMap<>(); - private final LwM2MAttributesService attributesService; private final TransportService transportService; private final LwM2mClientContext clientContext; + private final LwM2MTransportServerConfig config; + private final LwM2mUplinkMsgHandler uplinkHandler; + private final LwM2mDownlinkMsgHandler downlinkHandler; + private final OtaPackageDataCache otaPackageDataCache; + private final LwM2mTransportServerHelper helper; + private ExecutorService executor; + + @Autowired + @Lazy + private LwM2MAttributesService attributesService; + + @PostConstruct + public void init() { + //TODO: define parallelism in constant + executor = ThingsBoardExecutors.newWorkStealingPool(4, "LwM2M OTA Updates"); + } @Override public void init(LwM2mClient client) { + //TODO: add locks by client fwInfo. //TODO: check that the client supports FW and SW by checking the supported objects in the model. List attributesToFetch = new ArrayList<>(); - if (client.isValidObjectVersion(FW_NAME_ID) || client.isValidObjectVersion(FW_VER_ID)) { - LwM2MClientOtaState fwState = getOrInitFwSate(client); - attributesToFetch.add(getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE)); - attributesToFetch.add(getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION)); + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setSupported(client.isValidObjectVersion(FW_NAME_ID) || client.isValidObjectVersion(FW_VER_ID)); + if (fwInfo.isSupported()) { + attributesToFetch.add(FIRMWARE_TITLE); + attributesToFetch.add(FIRMWARE_VERSION); + attributesToFetch.add(FIRMWARE_URL); + } + + if (!attributesToFetch.isEmpty()) { + var future = attributesService.getSharedAttributes(client, attributesToFetch); + DonAsynchron.withCallback(future, attrs -> { + if (fwInfo.isSupported()) { + Optional newFirmwareTitle = getAttributeValue(attrs, FIRMWARE_TITLE); + Optional newFirmwareVersion = getAttributeValue(attrs, FIRMWARE_VERSION); + Optional newFirmwareUrl = getAttributeValue(attrs, FIRMWARE_URL); + if (newFirmwareTitle.isPresent() && newFirmwareVersion.isPresent()) { + onTargetFirmwareUpdate(client, newFirmwareTitle.get(), newFirmwareVersion.get(), newFirmwareUrl); + } + } + }, throwable -> { + if (fwInfo.isSupported()) { + fwInfo.setTargetFetchFailure(true); + } + }, executor); + } + } + + @Override + public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional newFirmwareUrl) { + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl); + startFirmwareUpdateIfNeeded(client, fwInfo); + } + + @Override + public void onCurrentFirmwareNameUpdate(LwM2mClient client, String name) { + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setCurrentName(name); + } + + @Override + public void onCurrentFirmwareVersionUpdate(LwM2mClient client, String version) { + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setCurrentVersion(version); + } + + @Override + public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long state) { + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + UpdateStateFw newState = UpdateStateFw.fromStateFwByCode(state.intValue()); + if (UpdateStateFw.DOWNLOADED.equals(newState)) { + executeFwUpdate(client); + } + fwInfo.setUpdateState(newState); + sendStateUpdateToTelemetry(client, fwInfo, LwM2mTransportUtil.toOtaPackageUpdateStatus(newState)); + } + + @Override + public void onCurrentFirmwareResultUpdate(LwM2mClient client, Long code) { + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + UpdateResultFw result = UpdateResultFw.fromUpdateResultFwByCode(code.intValue()); + sendStateUpdateToTelemetry(client, fwInfo, LwM2mTransportUtil.toOtaPackageUpdateStatus(result)); + if (result.isAgain && fwInfo.getRetryAttempts() <= 2) { + fwInfo.setRetryAttempts(fwInfo.getRetryAttempts() + 1); + startFirmwareUpdateIfNeeded(client, fwInfo); + } else { + fwInfo.setUpdateResult(result); } + } - if (client.isValidObjectVersion(SW_NAME_ID) || client.isValidObjectVersion(SW_VER_ID)) { - LwM2MClientOtaState swState = getOrInitSwSate(client); - attributesToFetch.add(getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE)); - attributesToFetch.add(getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION)); + @Override + public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion) { + + } + + private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo fwInfo) { + if (fwInfo.isUpdateRequired()) { + if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) { + log.info("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl()); + startFirmwareUpdateUsingUrl(client, fwInfo.getTargetUrl()); + } else { + startFirmwareUpdateUsingBinary(client, fwInfo); + } } + } + + private void startFirmwareUpdateUsingUrl(LwM2mClient client, String url) { + String targetIdVer = convertObjectIdToVersionedId(FW_URL_ID, client.getRegistration()); + TbLwM2MWriteUpdateRequest request = TbLwM2MWriteUpdateRequest.builder().versionedId(targetIdVer).value(url).timeout(config.getTimeout()).build(); + downlinkHandler.sendWriteUpdateRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, client, targetIdVer)); + } + + public void startFirmwareUpdateUsingBinary(LwM2mClient client, LwM2MClientOtaInfo fwInfo) { + String versionedId = convertObjectIdToVersionedId(FW_PACKAGE_5_ID, client.getRegistration()); + this.transportService.process(client.getSession(), createOtaPackageRequestMsg(client.getSession(), OtaPackageType.FIRMWARE.name()), + new TransportServiceCallback<>() { + @Override + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) + && response.getType().equals(OtaPackageType.FIRMWARE.name())) { + UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); + var strategy = fwInfo.getStrategy(); + switch (strategy) { + case OBJ_5_BINARY: + byte[] firmwareChunk = otaPackageDataCache.get(otaPackageId.toString(), 0, 0); + TbLwM2MWriteReplaceRequest writeRequest = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) + .value(firmwareChunk).contentFormat(ContentFormat.OPAQUE) + .timeout(config.getTimeout()).build(); + downlinkHandler.sendWriteReplaceRequest(client, writeRequest, new TbLwM2MWriteResponseCallback(uplinkHandler, client, versionedId)); + break; + case OBJ_5_TEMP_URL: + startFirmwareUpdateUsingUrl(client, fwInfo.getBaseUrl() + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + otaPackageId.toString()); + break; + default: + //TODO: send log to telemetry + } + } else { + //TODO: send log to telemetry + } + } - var future = attributesService.getSharedAttributes(client, attributesToFetch); + @Override + public void onError(Throwable e) { + log.trace("Failed to process firmwareUpdate ", e); + } + }); } - private LwM2MClientOtaState getOrInitFwSate(LwM2mClient client) { - //TODO: fetch state from the cache. - return fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> new LwM2MClientOtaState()); + private TransportProtos.GetOtaPackageRequestMsg createOtaPackageRequestMsg(TransportProtos.SessionInfoProto sessionInfo, String nameFwSW) { + return TransportProtos.GetOtaPackageRequestMsg.newBuilder() + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .setType(nameFwSW) + .build(); + } + + private void executeFwUpdate(LwM2mClient client) { + TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(FW_EXECUTE_ID).timeout(config.getTimeout()).build(); + downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(uplinkHandler, client, FW_EXECUTE_ID)); + } + + private Optional getAttributeValue(List attrs, String keyName) { + for (TransportProtos.TsKvProto attr : attrs) { + if (keyName.equals(attr.getKv().getKey())) { + if (attr.getKv().getType().equals(TransportProtos.KeyValueType.STRING_V)) { + return Optional.of(attr.getKv().getStringV()); + } else { + return Optional.empty(); + } + } + } + return Optional.empty(); + } + + private LwM2MClientOtaInfo getOrInitFwInfo(LwM2mClient client) { + //TODO: fetch state from the cache or DB. + return fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> { + var profile = clientContext.getProfile(client.getProfileId()); + return new LwM2MClientOtaInfo(endpoint, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy(), + profile.getClientLwM2mSettings().getFwUpdateRecourse()); + }); + } + + private LwM2MClientOtaInfo getOrInitSwInfo(LwM2mClient client) { + //TODO: fetch state from the cache or DB. + return swStates.computeIfAbsent(client.getEndpoint(), endpoint -> { + var profile = clientContext.getProfile(client.getProfileId()); + return new LwM2MClientOtaInfo(endpoint, OtaPackageType.SOFTWARE, profile.getClientLwM2mSettings().getSwUpdateStrategy(), profile.getClientLwM2mSettings().getSwUpdateRecourse()); + }); + } - private LwM2MClientOtaState getOrInitSwSate(LwM2mClient client) { - //TODO: fetch state from the cache. - return swStates.computeIfAbsent(client.getEndpoint(), endpoint -> new LwM2MClientOtaState()); + private void sendStateUpdateToTelemetry(LwM2mClient client, LwM2MClientOtaInfo fwInfo, OtaPackageUpdateStatus status) { + List result = new ArrayList<>(); + TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(fwInfo.getType(), STATE)); + kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(status.name()); + result.add(kvProto.build()); + helper.sendParametersOnThingsboardTelemetry(result, client.getSession()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java new file mode 100644 index 0000000000..26f7abed4f --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.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.transport.lwm2m.server.ota; + +import lombok.Data; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; +import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; + +import java.util.Optional; + +@Data +public class LwM2MClientOtaInfo { + + private final String endpoint; + private final OtaPackageType type; + + private String baseUrl; + private boolean supported; + + private boolean targetFetchFailure; + private String targetName; + private String targetVersion; + private String targetUrl; + + private boolean currentFetchFailure; + private String currentName; + private String currentVersion; + + //TODO: use value from device if applicable; + private LwM2MFirmwareUpdateStrategy strategy; + private UpdateStateFw updateState; + private UpdateResultFw updateResult; + + private String failedPackageId; + private int retryAttempts; + + public LwM2MClientOtaInfo(String endpoint, OtaPackageType type, Integer strategyCode, String baseUrl) { + this.endpoint = endpoint; + this.type = type; + this.strategy = LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(strategyCode); + this.baseUrl = baseUrl; + } + + public void updateTarget(String targetName, String targetVersion, Optional newFirmwareUrl) { + this.targetName = targetName; + this.targetVersion = targetVersion; + this.targetUrl = newFirmwareUrl.orElse(null); + } + + public boolean isUpdateRequired() { + if (StringUtils.isEmpty(targetName) || + StringUtils.isEmpty(targetVersion) || + (StringUtils.isEmpty(currentName) && StringUtils.isEmpty(currentVersion))) { + return false; + } else { + String targetPackageId = getPackageId(targetName, targetVersion); + String currentPackageId = getPackageId(currentName, currentVersion); + if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) { + return false; + } else { + return !targetPackageId.equals(currentPackageId); + } + } + } + + public void setUpdateResult(UpdateResultFw updateResult) { + this.updateResult = updateResult; + switch (updateResult) { + case INITIAL: + break; + case UPDATE_SUCCESSFULLY: + retryAttempts = 0; + break; + default: + failedPackageId = getPackageId(targetName, targetVersion); + break; + } + } + + private static String getPackageId(String name, String version) { + return (StringUtils.isNotEmpty(name) ? name : "") + (StringUtils.isNotEmpty(version) ? version : ""); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaState.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaState.java index 43dd874a09..b4201f3abd 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaState.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaState.java @@ -15,5 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.server.ota; -public class LwM2MClientOtaState { +public enum LwM2MClientOtaState { + + IDLE, IN_PROGRESS, SUCCESS, FAILED + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java index 9dcbab5d9e..e1bde7df8b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java @@ -17,8 +17,21 @@ package org.thingsboard.server.transport.lwm2m.server.ota; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import java.util.Optional; + public interface LwM2MOtaUpdateService { void init(LwM2mClient client); + void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional newFirmwareUrl); + + void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion); + + void onCurrentFirmwareNameUpdate(LwM2mClient client, String name); + + void onCurrentFirmwareVersionUpdate(LwM2mClient client, String version); + + void onCurrentFirmwareStateUpdate(LwM2mClient client, Long state); + + void onCurrentFirmwareResultUpdate(LwM2mClient client, Long result); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index b2c8f73ac8..533458ce76 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -238,7 +238,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { IdOrKeyRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), IdOrKeyRequest.class); String targetId; if (StringUtils.isNotEmpty(requestParams.getKey())) { - targetId = uplinkHandler.getObjectIdByKeyNameFromProfile(client, requestParams.getKey()); + targetId = clientContext.getObjectIdByKeyNameFromProfile(client, requestParams.getKey()); } else if (StringUtils.isNotEmpty(requestParams.getId())) { targetId = requestParams.getId(); } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index ec5fbbb6dd..97fe273857 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -68,23 +68,20 @@ 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.LwM2mFwSwUpdate; import org.thingsboard.server.transport.lwm2m.server.client.ParametersAnalyzeResult; -import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MDiscoverRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MLatchCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; @@ -109,8 +106,10 @@ import java.util.stream.Collectors; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper.getValueFromKvProto; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_3_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; @@ -119,9 +118,8 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey; @Slf4j @@ -148,9 +146,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { public final Map firmwareUpdateState; - public DefaultLwM2MUplinkMsgHandler(TransportService transportService, LwM2MAttributesService attributesService, LwM2MOtaUpdateService otaService, + public DefaultLwM2MUplinkMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, LwM2mClientContext clientContext, + @Lazy LwM2MOtaUpdateService otaService, + @Lazy LwM2MAttributesService attributesService, @Lazy LwM2MRpcRequestHandler rpcHandler, @Lazy LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler, OtaPackageDataCache otaPackageDataCache, @@ -561,21 +561,16 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private void updateResourcesValue(LwM2mClient lwM2MClient, LwM2mResource lwM2mResource, String path) { Registration registration = lwM2MClient.getRegistration(); if (lwM2MClient.saveResourceValue(path, lwM2mResource, this.config.getModelProvider())) { - /** version != null - * set setClient_fw_info... = value - **/ - if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { - lwM2MClient.getFwUpdate().initReadValue(this, this.defaultLwM2MDownlinkMsgHandler, path); - } - if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { - lwM2MClient.getSwUpdate().initReadValue(this, this.defaultLwM2MDownlinkMsgHandler, path); - } - - if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path)) || - (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) { - LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(this, clientContext); - log.warn("93) path: [{}] value: [{}]", path, lwM2mResource.getValue()); - fwUpdate.updateStateOta(this, defaultLwM2MDownlinkMsgHandler, registration, path, ((Long) lwM2mResource.getValue()).intValue()); + if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) { + otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) { + otaService.onCurrentFirmwareVersionUpdate(lwM2MClient, (String) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(FW_5_VER_ID, registration))) { + otaService.onCurrentFirmwareVersionUpdate(lwM2MClient, (String) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(FW_STATE_ID, registration))) { + otaService.onCurrentFirmwareStateUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(FW_RESULT_ID, registration))) { + otaService.onCurrentFirmwareResultUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); } this.updateAttrTelemetry(registration, Collections.singleton(path)); } else { @@ -684,7 +679,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { String resourceName = names.get(pathIdVer); if (resourceName != null && !resourceName.isEmpty()) { try { - LwM2mResource resourceValue = getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); + LwM2mResource resourceValue = LwM2mTransportUtil.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); if (resourceValue != null) { ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); @@ -720,38 +715,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { return null; } - /** - * @param pathIdVer - path resource - * @return - value of Resource into format KvProto or null - */ - private Object getResourceValueFormatKv(LwM2mClient lwM2MClient, String pathIdVer) { - LwM2mResource resourceValue = this.getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); - if (resourceValue != null) { - ResourceModel.Type currentType = resourceValue.getType(); - ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); - return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, - new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); - } else { - return null; - } - } - - /** - * @param lwM2MClient - - * @param path - - * @return - return value of Resource by idPath - */ - private LwM2mResource getResourceValueFromLwM2MClient(LwM2mClient lwM2MClient, String path) { - LwM2mResource lwm2mResourceValue = null; - ResourceValue resourceValue = lwM2MClient.getResources().get(path); - if (resourceValue != null) { - if (new LwM2mPath(fromVersionedIdToObjectId(path)).isResource()) { - lwm2mResourceValue = lwM2MClient.getResources().get(path).getLwM2mResource(); - } - } - return lwm2mResourceValue; - } - @Override public void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request) { if (request.getNode() instanceof LwM2mResource) { @@ -890,19 +853,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } } - private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { - if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { - TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(this, lwM2MClient, versionedId)); - } else { - log.error("Failed update resource [{}] [{}]", versionedId, newValue); - String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", - LOG_LWM2M_ERROR, versionedId, newValue); - this.logToTelemetry(lwM2MClient, logMsg); - log.info("Failed update resource [{}] [{}]", versionedId, newValue); - } - } - /** * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) * config attr/telemetry... in profile @@ -912,59 +862,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); } - /** - * Get path to resource from profile equal keyName - * - * @param sessionInfo - - * @param keyName - - * @return - - */ - @Override - public String getObjectIdByKeyNameFromProfile(TransportProtos.SessionInfoProto sessionInfo, String keyName) { - return getObjectIdByKeyNameFromProfile(clientContext.getClientBySessionInfo(sessionInfo), keyName); - } - - @Override - public String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName) { - Lwm2mDeviceProfileTransportConfiguration profile = clientContext.getProfile(lwM2mClient.getProfileId()); - - return profile.getObserveAttr().getKeyName().entrySet().stream() - .filter(e -> e.getValue().equals(keyName) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().orElseThrow( - () -> new IllegalArgumentException(keyName + " is not configured in the device profile!") - ).getKey(); - } - - /** - * #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 - */ - public void onAttributesUpdate(LwM2mClient lwM2MClient, List tsKvProtos) { - log.trace("[{}] onAttributesUpdate [{}]", lwM2MClient.getEndpoint(), tsKvProtos); - tsKvProtos.forEach(tsKvProto -> { - String pathIdVer = this.getObjectIdByKeyNameFromProfile(lwM2MClient, tsKvProto.getKv().getKey()); - if (pathIdVer != null) { - // #1.1 - if (lwM2MClient.getSharedAttributes().containsKey(pathIdVer)) { - if (tsKvProto.getTs() > lwM2MClient.getSharedAttributes().get(pathIdVer).getTs()) { - lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); - } - } else { - lwM2MClient.getSharedAttributes().put(pathIdVer, tsKvProto); - } - } - }); - // #2.1 - lwM2MClient.getSharedAttributes().forEach((pathIdVer, tsKvProto) -> { - this.pushUpdateToClientIfNeeded(lwM2MClient, this.getResourceValueFormatKv(lwM2MClient, pathIdVer), - getValueFromKvProto(tsKvProto.getKv()), pathIdVer); - }); - } - /** * @param lwM2MClient - * @return SessionInfoProto - @@ -1017,7 +914,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { keysToFetch.removeAll(OtaPackageUtil.ALL_FW_ATTRIBUTE_KEYS); keysToFetch.removeAll(OtaPackageUtil.ALL_SW_ATTRIBUTE_KEYS); DonAsynchron.withCallback(attributesService.getSharedAttributes(lwM2MClient, keysToFetch), - v -> onAttributesUpdate(lwM2MClient, v), + v -> attributesService.onAttributesUpdate(lwM2MClient, v), t -> log.error("[{}] Failed to get attributes", lwM2MClient.getEndpoint(), t), registrationExecutor); } @@ -1045,7 +942,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { fwUpdate.setCurrentTitle(response.getTitle()); fwUpdate.setCurrentId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())); // if (rpcRequest == null) { - fwUpdate.sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); + fwUpdate.sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); // } else { // fwUpdate.writeFwSwWare(handler, defaultLwM2MDownlinkMsgHandler); // } @@ -1059,7 +956,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); log.trace(msgError); // if (rpcRequest != null) { - //TODO: refactor + //TODO: refactor // sendErrorRpcResponse(rpcRequest, msgError, sessionInfo); // } } @@ -1091,7 +988,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { lwM2MClient.getSwUpdate().setCurrentId(new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())).getId()); lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); // if (rpcRequest == null) { - lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); + lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); // } else { // lwM2MClient.getSwUpdate().writeFwSwWare(handler, defaultLwM2MDownlinkMsgHandler); // } @@ -1124,16 +1021,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { return profile.getObserveAttr().getKeyName(); } - private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { - ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config - .getModelProvider()); - Integer objectId = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)).getObjectId(); - String objectVer = validateObjectVerFromKey(pathIdVer); - return resourceModel != null && (isWritableNotOptional ? - objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId)) && resourceModel.operations.isWritable() : - objectId != null && objectVer != null && objectVer.equals(lwM2mClient.getRegistration().getSupportedVersion(objectId))); - } - public LwM2MTransportServerConfig getConfig() { return this.config; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java index 041af97474..517db388e4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java @@ -60,9 +60,5 @@ public interface LwM2mUplinkMsgHandler { void onToTransportUpdateCredentials(TransportProtos.ToTransportUpdateCredentialsProto updateCredentials); - String getObjectIdByKeyNameFromProfile(TransportProtos.SessionInfoProto sessionInfo, String name); - - String getObjectIdByKeyNameFromProfile(LwM2mClient lwM2mClient, String keyName); - LwM2MTransportServerConfig getConfig(); } From 079ae5fd8f17bb7b6f788372e7734bc5e03a8796 Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Wed, 16 Jun 2021 23:06:52 +0300 Subject: [PATCH 387/476] UI: Refactoring resource: add help link; improvement text and view --- .../resources-library-table-config.resolve.ts | 10 +++---- .../resource/resources-library.component.html | 24 ++++++++++++++--- .../resource/resources-library.component.ts | 27 ++++++++++++++----- ui-ngx/src/app/shared/models/constants.ts | 1 + .../src/app/shared/models/resource.models.ts | 4 +-- .../assets/locale/locale.constant-en_US.json | 7 +++-- .../assets/locale/locale.constant-zh_CN.json | 1 - 7 files changed, 54 insertions(+), 20 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts index 089d4b3d82..96745fa47a 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library-table-config.resolve.ts @@ -69,10 +69,10 @@ export class ResourcesLibraryTableConfigResolver implements Resolve true, - onAction: ($event, entity) => this.exportResource($event, entity) + onAction: ($event, entity) => this.downloadResource($event, entity) } ); @@ -118,7 +118,7 @@ export class ResourcesLibraryTableConfigResolver implements Resolve): boolean { switch (action.action) { - case 'uploadResource': - this.exportResource(action.event, action.entity); + case 'downloadResource': + this.downloadResource(action.event, action.entity); return true; } return false; diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html index 5b98fa9c61..1f33d63776 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.html @@ -18,16 +18,26 @@
+
+ +
@@ -47,7 +57,7 @@ {{ 'resource.title-required' | translate }} - +
+ + resource.file-name + + +
diff --git a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts index ce26499102..50d565ccc0 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/resource/resources-library.component.ts @@ -30,6 +30,7 @@ import { ResourceTypeTranslationMap } from '@shared/models/resource.models'; import { pairwise, startWith, takeUntil } from 'rxjs/operators'; +import { ActionNotificationShow } from "@core/notification/notification.actions"; @Component({ selector: 'tb-resources-library', @@ -88,26 +89,29 @@ export class ResourcesLibraryComponent extends EntityComponent impleme } buildForm(entity: Resource): FormGroup { - return this.fb.group( + const form = this.fb.group( { + title: [entity ? entity.title : '', []], resourceType: [{ value: entity?.resourceType ? entity.resourceType : ResourceType.LWM2M_MODEL, - disabled: this.isEdit + disabled: !this.isAdd }, [Validators.required]], - data: [entity ? entity.data : null, [Validators.required]], fileName: [entity ? entity.fileName : null, [Validators.required]], - title: [entity ? entity.title : '', []] } ); + if (this.isAdd) { + form.addControl('data', this.fb.control(null, Validators.required)); + } + return form } updateForm(entity: Resource) { - this.entityForm.patchValue({resourceType: entity.resourceType}); if (this.isEdit) { this.entityForm.get('resourceType').disable({emitEvent: false}); + this.entityForm.get('fileName').disable({emitEvent: false}); } this.entityForm.patchValue({ - data: entity.data, + resourceType: entity.resourceType, fileName: entity.fileName, title: entity.title }); @@ -132,4 +136,15 @@ export class ResourcesLibraryComponent extends EntityComponent impleme convertToBase64File(data: string): string { return window.btoa(data); } + + onResourceIdCopied() { + this.store.dispatch(new ActionNotificationShow( + { + message: this.translate.instant('resource.idCopiedMessage'), + type: 'success', + duration: 750, + verticalPosition: 'bottom', + horizontalPosition: 'right' + })); + } } diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index b72e2b196d..56347f1261 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -120,6 +120,7 @@ export const HelpLinks = { entityViews: helpBaseUrl + '/docs/user-guide/ui/entity-views', entitiesImport: helpBaseUrl + '/docs/user-guide/bulk-provisioning', rulechains: helpBaseUrl + '/docs/user-guide/ui/rule-chains', + resources: helpBaseUrl + '/docs/user-guide/ui/resources', dashboards: helpBaseUrl + '/docs/user-guide/ui/dashboards', otaUpdates: helpBaseUrl + '/docs/user-guide/ui/ota-updates', widgetsBundles: helpBaseUrl + '/docs/user-guide/ui/widget-library#bundles', diff --git a/ui-ngx/src/app/shared/models/resource.models.ts b/ui-ngx/src/app/shared/models/resource.models.ts index 082d09ac24..9c478cca0d 100644 --- a/ui-ngx/src/app/shared/models/resource.models.ts +++ b/ui-ngx/src/app/shared/models/resource.models.ts @@ -61,6 +61,6 @@ export interface Resource extends ResourceInfo { } export interface Resources extends ResourceInfo { - data: string|string[]; - fileName: string|string[]; + data: Array; + fileName: Array; } 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 ec084b8a3a..172cc2033d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2309,20 +2309,23 @@ }, "resource": { "add": "Add Resource", + "copyId": "Copy resource Id", "delete": "Delete resource", "delete-resource-text": "Be careful, after the confirmation the resource will become unrecoverable.", "delete-resource-title": "Are you sure you want to delete the resource '{{resourceTitle}}'?", "delete-resources-action-title": "Delete { count, plural, 1 {1 resource} other {# resources} }", "delete-resources-text": "Be careful, after the confirmation all selected resources will be removed.", "delete-resources-title": "Are you sure you want to delete { count, plural, 1 {1 resource} other {# resources} }?", + "download": "Download resource", "drop-file": "Drop a resource file or click to select a file to upload.", "empty": "Resource is empty", - "export": "Export resource", + "file-name": "File name", + "idCopiedMessage": "Resource Id has been copied to clipboard", "no-resource-matching": "No resource matching '{{widgetsBundle}}' were found.", "no-resource-text": "No resources found", "open-widgets-bundle": "Open widgets bundle", "resource": "Resource", - "resource-library-details": "Resource library details", + "resource-library-details": "Resource details", "resource-type": "Resource type", "resources-library": "Resources library", "search": "Search resources", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index 6a60db96fc..cc23f5fc78 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -1994,7 +1994,6 @@ "delete-resources-title": "确定要删除 { count, plural, 1 {# 个资源} other {# 个资源} }?", "drop-file": "拖拽资源文件或单击以选择要上传的文件。", "empty": "资源为空", - "export": "导出资源", "no-resource-matching": "找不到与 '{{widgetsBundle}}' 匹配的资源。", "no-resource-text": "找不到资源", "open-widgets-bundle": "打开部件库", From cda6f3678b8cfe7a0046d75ff5daeae3287b8312 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 07:27:42 +0300 Subject: [PATCH 388/476] js-executor: fixed license header --- msa/js-executor/api/httpServer.js | 36 +++++++++---------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/msa/js-executor/api/httpServer.js b/msa/js-executor/api/httpServer.js index 996638c7c3..4948c45f61 100644 --- a/msa/js-executor/api/httpServer.js +++ b/msa/js-executor/api/httpServer.js @@ -1,34 +1,18 @@ /* - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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. */ - const config = require('config'), logger = require('../config/logger')._logger('httpServer'), express = require('express'); From c07379812b213f3ca249da31285a00124d6345bd Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 07:28:53 +0300 Subject: [PATCH 389/476] js-executor: updated yarn.lock --- msa/js-executor/yarn.lock | 361 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 347 insertions(+), 14 deletions(-) diff --git a/msa/js-executor/yarn.lock b/msa/js-executor/yarn.lock index 404c059c23..0092372b69 100644 --- a/msa/js-executor/yarn.lock +++ b/msa/js-executor/yarn.lock @@ -418,6 +418,14 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" +accepts@~1.3.7: + version "1.3.7" + resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" + integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== + dependencies: + mime-types "~2.1.24" + negotiator "0.6.2" + agent-base@6: version "6.0.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" @@ -487,6 +495,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + array-union@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" @@ -621,6 +634,22 @@ bluebird@^3.5.2: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +body-parser@1.19.0: + version "1.19.0" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" + integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== + dependencies: + bytes "3.1.0" + content-type "~1.0.4" + debug "2.6.9" + depd "~1.1.2" + http-errors "1.7.2" + iconv-lite "0.4.24" + on-finished "~2.3.0" + qs "6.7.0" + raw-body "2.4.0" + type-is "~1.6.17" + boxen@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64" @@ -682,6 +711,11 @@ byline@^5.0.0: resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" integrity sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE= +bytes@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" + integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== + cacheable-request@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-6.1.0.tgz#20ffb8bd162ba4be11e9567d823db651052ca912" @@ -838,6 +872,28 @@ configstore@^5.0.1: write-file-atomic "^3.0.0" xdg-basedir "^4.0.0" +content-disposition@0.5.3: + version "0.5.3" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" + integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== + dependencies: + safe-buffer "5.1.2" + +content-type@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" + integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== + +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= + +cookie@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" + integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -867,6 +923,13 @@ dateformat@1.0.2-1.2.3: dependencies: ms "^2.1.1" +debug@2.6.9, debug@^2.2.0, debug@~2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + debug@4, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" @@ -874,13 +937,6 @@ debug@4, debug@^4.1.1: dependencies: ms "^2.1.1" -debug@^2.2.0, debug@~2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -913,6 +969,16 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +depd@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + +destroy@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" + integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -962,6 +1028,11 @@ ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11: dependencies: safe-buffer "^5.0.1" +ee-first@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" + integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -977,6 +1048,11 @@ enabled@2.0.x: resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" integrity sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ== +encodeurl@~1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -994,6 +1070,11 @@ escape-goat@^2.0.0: resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" integrity sha512-8/uIhbG12Csjy2JEW7D9pHbreaVaS/OpN3ycnyvElTdwM5n6GY6W6e2IPemfvGZeUMqZ9A/3GqIZMgKnBhAw/Q== +escape-html@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" + integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + escodegen@^1.14.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" @@ -1021,6 +1102,11 @@ esutils@^2.0.2: resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +etag@~1.8.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" + integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -1041,6 +1127,42 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== +express@^4.17.1: + version "4.17.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" + integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== + dependencies: + accepts "~1.3.7" + array-flatten "1.1.1" + body-parser "1.19.0" + content-disposition "0.5.3" + content-type "~1.0.4" + cookie "0.4.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "~1.1.2" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "~1.1.2" + fresh "0.5.2" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "~2.3.0" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.5" + qs "6.7.0" + range-parser "~1.2.1" + safe-buffer "5.1.2" + send "0.17.1" + serve-static "1.14.1" + setprototypeof "1.1.1" + statuses "~1.5.0" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + extend@^3.0.2, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" @@ -1119,6 +1241,19 @@ fill-range@^7.0.1: dependencies: to-regex-range "^5.0.1" +finalhandler@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" + integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "~2.3.0" + parseurl "~1.3.3" + statuses "~1.5.0" + unpipe "~1.0.0" + find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1155,6 +1290,16 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + +fresh@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" + integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + from2@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -1384,6 +1529,28 @@ http-cache-semantics@^4.0.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== +http-errors@1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" + integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== + dependencies: + depd "~1.1.2" + inherits "2.0.3" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + +http-errors@~1.7.2: + version "1.7.3" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" + integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== + dependencies: + depd "~1.1.2" + inherits "2.0.4" + setprototypeof "1.1.1" + statuses ">= 1.5.0 < 2" + toidentifier "1.0.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -1401,6 +1568,13 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +iconv-lite@0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ieee754@1.1.13, ieee754@^1.1.4: version "1.1.13" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.13.tgz#ec168558e95aa181fd87d37f55c32bbcb6708b84" @@ -1431,7 +1605,7 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: +inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1449,6 +1623,11 @@ into-stream@^5.1.1: from2 "^2.3.0" p-is-promise "^3.0.0" +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + is-arrayish@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" @@ -1764,11 +1943,26 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" @@ -1782,6 +1976,11 @@ mime-db@1.44.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== +mime-db@1.48.0: + version "1.48.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" + integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== + mime-types@^2.1.12, mime-types@~2.1.19: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" @@ -1789,6 +1988,18 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "1.44.0" +mime-types@~2.1.24: + version "2.1.31" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" + integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== + dependencies: + mime-db "1.48.0" + +mime@1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + mime@^2.2.0: version "2.4.6" resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1" @@ -1833,6 +2044,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + ms@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" @@ -1846,6 +2062,11 @@ multistream@^2.1.1: inherits "^2.0.1" readable-stream "^2.0.5" +negotiator@0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" + integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== + node-fetch@^2.3.0, node-fetch@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" @@ -1899,6 +2120,13 @@ object-hash@^2.0.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-2.0.3.tgz#d12db044e03cd2ca3d77c0570d87225b02e1e6ea" integrity sha512-JPKn0GMu+Fa3zt3Bmr66JhokJU5BaNBIh4ZeTlaCBzrBsOeXzwcKKAK1tbLiPKgvwmPXsDvvLHoWh5Bm7ofIYg== +on-finished@~2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" + integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= + dependencies: + ee-first "1.1.1" + once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -1974,6 +2202,11 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" +parseurl@~1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" + integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -1984,6 +2217,11 @@ path-parse@^1.0.6: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -2079,6 +2317,14 @@ protobufjs@^6.8.6, protobufjs@^6.9.0: "@types/node" "^13.7.0" long "^4.0.0" +proxy-addr@~2.0.5: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + psl@^1.1.28, psl@^1.1.33: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -2114,6 +2360,11 @@ pupa@^2.0.1: dependencies: escape-goat "^2.0.0" +qs@6.7.0: + version "6.7.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" + integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -2129,6 +2380,21 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +range-parser@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" + integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== + +raw-body@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" + integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== + dependencies: + bytes "3.1.0" + http-errors "1.7.2" + iconv-lite "0.4.24" + unpipe "1.0.0" + rc@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -2292,17 +2558,17 @@ run-parallel@^1.1.9: resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== +safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1, safe-buffer@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== -safe-buffer@~5.1.0, safe-buffer@~5.1.1, safe-buffer@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -2339,11 +2605,45 @@ semver@^7.1.3: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== +send@0.17.1: + version "0.17.1" + resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" + integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== + dependencies: + debug "2.6.9" + depd "~1.1.2" + destroy "~1.0.4" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "~1.7.2" + mime "1.6.0" + ms "2.1.1" + on-finished "~2.3.0" + range-parser "~1.2.1" + statuses "~1.5.0" + +serve-static@1.14.1: + version "1.14.1" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" + integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== + dependencies: + encodeurl "~1.0.2" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "0.17.1" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +setprototypeof@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" + integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== + signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -2391,6 +2691,11 @@ stack-trace@0.0.x: resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= +"statuses@>= 1.5.0 < 2", statuses@~1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" + integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= + stream-browserify@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -2513,6 +2818,11 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +toidentifier@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" + integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== + touch@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/touch/-/touch-3.1.0.tgz#fe365f5f75ec9ed4e56825e0bb76d24ab74af83b" @@ -2581,6 +2891,14 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-is@~1.6.17, type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" @@ -2636,6 +2954,11 @@ universalify@^1.0.0: resolved "https://registry.yarnpkg.com/universalify/-/universalify-1.0.0.tgz#b61a1da173e8435b2fe3c67d29b9adf8594bd16d" integrity sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug== +unpipe@1.0.0, unpipe@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + update-notifier@^4.0.0: version "4.1.1" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-4.1.1.tgz#895fc8562bbe666179500f9f2cebac4f26323746" @@ -2705,6 +3028,11 @@ util@^0.11.1: dependencies: inherits "2.0.3" +utils-merge@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" + integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + uuid-parse@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/uuid-parse/-/uuid-parse-1.1.0.tgz#7061c5a1384ae0e1f943c538094597e1b5f3a65b" @@ -2735,6 +3063,11 @@ validator@^9.4.1: resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== +vary@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= + verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" From d117cec09e25c52ea69807a4d4db051505a3b934 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 07:42:21 +0300 Subject: [PATCH 390/476] js-executor: partitions_consumed_concurrently marked as experimental --- msa/js-executor/config/custom-environment-variables.yml | 2 +- msa/js-executor/config/default.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index 4185e65835..fdf261e25c 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -29,7 +29,7 @@ kafka: acks: "TB_KAFKA_ACKS" # -1 = all; 0 = no acknowledgments; 1 = only waits for the leader to acknowledge batch_size: "TB_KAFKA_BATCH_SIZE" # for producer linger_ms: "TB_KAFKA_LINGER_MS" # for producer - partitions_consumed_concurrently: "TB_KAFKA_PARTITIONS_CONSUMED_CONCURRENTLY" # increase this value if you are planning to handle more than one partition (scale up, scale down) - this will decrease the latency + partitions_consumed_concurrently: "TB_KAFKA_PARTITIONS_CONSUMED_CONCURRENTLY" # (EXPERIMENTAL) increase this value if you are planning to handle more than one partition (scale up, scale down) - this will decrease the latency requestTimeout: "TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS" compression: "TB_QUEUE_KAFKA_COMPRESSION" # gzip or uncompressed topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 190cf097ed..7a6e7c2469 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -29,7 +29,7 @@ kafka: acks: "1" # -1 = all; 0 = no acknowledgments; 1 = only waits for the leader to acknowledge batch_size: "128" # for producer linger_ms: "1" # for producer - partitions_consumed_concurrently: "1" # increase this value if you are planning to handle more than one partition (scale up, scale down) - this will decrease the latency + partitions_consumed_concurrently: "1" # (EXPERIMENTAL) increase this value if you are planning to handle more than one partition (scale up, scale down) - this will decrease the latency 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" From a3b31337cae7f0b639285855fd1cc050d05cfda4 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 5 May 2021 09:09:12 +0300 Subject: [PATCH 391/476] sync method replaced with async executeGenerateAsync for ScriptEngine api. affected Generator node, ruleChainController (merged with ce) --- .../controller/RuleChainController.java | 4 +- .../service/script/RemoteJsInvokeService.java | 8 ++- .../script/RuleNodeJsScriptEngine.java | 19 ++++--- .../rule/engine/api/ScriptEngine.java | 2 +- .../rule/engine/debug/TbMsgGeneratorNode.java | 50 ++++++++++++------- 5 files changed, 54 insertions(+), 29 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 d5fcc2706f..3b9af2f0f2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -74,6 +74,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j @@ -86,6 +87,7 @@ public class RuleChainController extends BaseController { public static final String RULE_NODE_ID = "ruleNodeId"; private static final ObjectMapper objectMapper = new ObjectMapper(); + public static final int TIMEOUT = 20; @Autowired private InstallScripts installScripts; @@ -391,7 +393,7 @@ public class RuleChainController extends BaseController { output = msgToOutput(engine.executeUpdate(inMsg)); break; case "generate": - output = msgToOutput(engine.executeGenerate(inMsg)); + output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); break; case "filter": boolean result = engine.executeFilter(inMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 334a471973..1bc9203333 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.script; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -26,6 +25,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.util.StopWatch; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.queue.TbQueueRequestTemplate; @@ -161,6 +161,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Override protected ListenableFuture doInvokeFunction(UUID scriptId, String functionName, Object[] args) { + log.trace("doInvokeFunction js-request for uuid {} with timeout {}ms", scriptId, maxRequestsTimeout); String scriptBody = scriptIdToBodysMap.get(scriptId); if (scriptBody == null) { return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!")); @@ -180,6 +181,9 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .setInvokeRequest(jsRequestBuilder.build()) .build(); + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + ListenableFuture> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); if (maxRequestsTimeout > 0) { future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); @@ -201,6 +205,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { } }, callbackExecutor); return Futures.transform(future, response -> { + stopWatch.stop(); + log.trace("doInvokeFunction js-response took {}ms for uuid {}", stopWatch.getTotalTimeMillis(), response.getKey()); JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse(); if (invokeResult.getSuccess()) { return invokeResult.getResult(); 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 066ce71a58..c688d6978d 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 @@ -102,7 +102,6 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); } catch (Throwable th) { - th.printStackTrace(); throw new RuntimeException("Failed to unbind message data from javascript result", th); } } @@ -141,13 +140,16 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } @Override - public TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException { - JsonNode result = executeScript(prevMsg); - if (!result.isObject()) { - log.warn("Wrong result type: {}", result.getNodeType()); - throw new ScriptException("Wrong result type: " + result.getNodeType()); - } - return unbindMsg(result, prevMsg); + public ListenableFuture executeGenerateAsync(TbMsg prevMsg) { + log.trace("execute generate async, prevMsg {}", prevMsg); + return Futures.transformAsync(executeScriptAsync(prevMsg), result -> { + if (!result.isObject()) { + log.warn("Wrong result type: {}", result.getNodeType()); + throw new ScriptException("Wrong result type: " + result.getNodeType()); + } + return Futures.immediateFuture(unbindMsg(result, prevMsg)); + }, MoreExecutors.directExecutor()); + } @Override @@ -234,6 +236,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } private ListenableFuture executeScriptAsync(TbMsg msg) { + log.trace("execute script async, msg {}", msg); String[] inArgs = prepareArgs(msg); return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]), o -> { diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java index 49420e8feb..a5dcf535e4 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java @@ -29,7 +29,7 @@ public interface ScriptEngine { ListenableFuture> executeUpdateAsync(TbMsg msg); - TbMsg executeGenerate(TbMsg prevMsg) throws ScriptException; + ListenableFuture executeGenerateAsync(TbMsg prevMsg); boolean executeFilter(TbMsg msg) throws ScriptException; 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 b5a47b4a0d..8fa4b97063 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 @@ -15,9 +15,12 @@ */ package org.thingsboard.rule.engine.debug; +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.springframework.util.StringUtils; +import org.thingsboard.common.util.TbStopWatch; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; @@ -35,6 +38,7 @@ import org.thingsboard.server.common.msg.queue.ServiceQueue; import java.util.UUID; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import static org.thingsboard.common.util.DonAsynchron.withCallback; import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; @@ -64,10 +68,11 @@ public class TbMsgGeneratorNode implements TbNode { private EntityId originatorId; private UUID nextTickId; private TbMsg prevMsg; - private volatile boolean initialized; + private final AtomicBoolean initialized = new AtomicBoolean(false); @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { + log.trace("init generator with config {}", configuration); this.config = TbNodeUtils.convert(configuration, TbMsgGeneratorNodeConfiguration.class); this.delay = TimeUnit.SECONDS.toMillis(config.getPeriodInSeconds()); this.currentMsgCount = 0; @@ -81,35 +86,39 @@ public class TbMsgGeneratorNode implements TbNode { @Override public void onPartitionChangeMsg(TbContext ctx, PartitionChangeMsg msg) { + log.trace("onPartitionChangeMsg, PartitionChangeMsg {}, config {}", msg, config); updateGeneratorState(ctx); } private void updateGeneratorState(TbContext ctx) { + log.trace("updateGeneratorState, config {}", config); if (ctx.isLocalEntity(originatorId)) { - if (!initialized) { - initialized = true; + if (initialized.compareAndSet(false, true)) { this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType"); scheduleTickMsg(ctx); } - } else if (initialized) { - initialized = false; + } else if (initialized.compareAndSet(true, false)) { destroy(); } } @Override public void onMsg(TbContext ctx, TbMsg msg) { - if (initialized && msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { + log.trace("onMsg, config {}, msg {}", config, msg); + if (initialized.get() && msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) { + TbStopWatch sw = TbStopWatch.startNew(); withCallback(generate(ctx, msg), m -> { - if (initialized && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { + log.trace("onMsg onSuccess callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg); + if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.enqueueForTellNext(m, SUCCESS); scheduleTickMsg(ctx); currentMsgCount++; } }, t -> { - if (initialized && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { + log.warn("onMsg onFailure callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg); + if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.tellFailure(msg, t); scheduleTickMsg(ctx); currentMsgCount++; @@ -119,6 +128,7 @@ public class TbMsgGeneratorNode implements TbNode { } private void scheduleTickMsg(TbContext ctx) { + log.trace("scheduleTickMsg, config {}", config); long curTs = System.currentTimeMillis(); if (lastScheduledTs == 0L) { lastScheduledTs = curTs; @@ -131,22 +141,26 @@ public class TbMsgGeneratorNode implements TbNode { } private ListenableFuture generate(TbContext ctx, TbMsg msg) { - return ctx.getJsExecutor().executeAsync(() -> { - if (prevMsg == null) { - prevMsg = ctx.newMsg(ServiceQueue.MAIN, "", originatorId, msg.getCustomerId(), new TbMsgMetaData(), "{}"); - } - if (initialized) { - ctx.logJsEvalRequest(); - TbMsg generated = jsEngine.executeGenerate(prevMsg); + log.trace("generate, config {}", config); + if (prevMsg == null) { + prevMsg = ctx.newMsg(ServiceQueue.MAIN, "", originatorId, msg.getCustomerId(), new TbMsgMetaData(), "{}"); + } + if (initialized.get()) { + ctx.logJsEvalRequest(); + return Futures.transformAsync(jsEngine.executeGenerateAsync(prevMsg), generated -> { + log.trace("generate process response, generated {}, config {}", generated, config); ctx.logJsEvalResponse(); prevMsg = ctx.newMsg(ServiceQueue.MAIN, generated.getType(), originatorId, msg.getCustomerId(), generated.getMetaData(), generated.getData()); - } - return prevMsg; - }); + return Futures.immediateFuture(prevMsg); + }, MoreExecutors.directExecutor()); + } + return Futures.immediateFuture(prevMsg); + } @Override public void destroy() { + log.trace("destroy, config {}", config); prevMsg = null; if (jsEngine != null) { jsEngine.destroy(); From 6888a1b349de234db1f6f3a8b81326fb34f438b1 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 5 May 2021 08:22:38 +0300 Subject: [PATCH 392/476] new utility class tbStopWatch added to improve performance measurement experience --- common/util/pom.xml | 5 ++ .../thingsboard/common/util/TbStopWatch.java | 62 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java diff --git a/common/util/pom.xml b/common/util/pom.xml index 6f66da5790..128bc478d5 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -36,6 +36,11 @@ + + org.springframework + spring-core + ${spring.version} + com.google.guava guava diff --git a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java new file mode 100644 index 0000000000..b7dbd0052c --- /dev/null +++ b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java @@ -0,0 +1,62 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.common.util; + +import org.springframework.util.StopWatch; + +/** + * Utility method that extends Spring Framework StopWatch + * It is a MONOTONIC time stopwatch. + * It is a replacement for any measurements with a wall-clock like System.currentTimeMillis() + * It is not affected by leap second, day-light saving and wall-clock adjustments by manual or network time synchronization + * The main features is a single call for common use cases: + * - create and start: TbStopWatch sw = TbStopWatch.startNew() + * - stop and get: sw.stopAndGetTotalTimeMillis() or sw.stopAndGetLastTaskTimeMillis() + * */ +public class TbStopWatch extends StopWatch { + + public static TbStopWatch startNew(){ + TbStopWatch stopWatch = new TbStopWatch(); + stopWatch.start(); + return stopWatch; + } + + public long stopAndGetTotalTimeMillis(){ + stop(); + return getTotalTimeMillis(); + } + + public long stopAndGetLastTaskTimeMillis(){ + stop(); + return getLastTaskTimeMillis(); + } + +} From f4c98595cb973f1c74f76c647d7bf4c4334851e9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 11:07:59 +0300 Subject: [PATCH 393/476] generator node: comment added on direct executor --- .../org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8fa4b97063..ab9c68e41f 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 @@ -152,7 +152,7 @@ public class TbMsgGeneratorNode implements TbNode { ctx.logJsEvalResponse(); prevMsg = ctx.newMsg(ServiceQueue.MAIN, generated.getType(), originatorId, msg.getCustomerId(), generated.getMetaData(), generated.getData()); return Futures.immediateFuture(prevMsg); - }, MoreExecutors.directExecutor()); + }, MoreExecutors.directExecutor()); //usually it runs on js-executor-remote-callback thread pool } return Futures.immediateFuture(prevMsg); From 585f473bdaa9477fd25616982dcc34ba80d50ed9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 5 May 2021 15:00:00 +0300 Subject: [PATCH 394/476] exception message added onFailure for GeneratorNode --- .../org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ab9c68e41f..cb9e25d34b 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 @@ -117,7 +117,7 @@ public class TbMsgGeneratorNode implements TbNode { } }, t -> { - log.warn("onMsg onFailure callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg); + log.warn("onMsg onFailure callback, took {}ms, config {}, msg {}, exception {}", sw.stopAndGetTotalTimeMillis(), config, msg, t); if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.tellFailure(msg, t); scheduleTickMsg(ctx); From a04eac6015bc98e626f446cfa9b6c4be802df626 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 27 Apr 2021 18:26:52 +0300 Subject: [PATCH 395/476] added logs and timing metrics for DefaultTbQueueRequestTemplate, TbKafkaConsumerTemplate (todo revert or refactor) --- .../queue/common/DefaultTbQueueRequestTemplate.java | 8 ++++---- .../server/queue/kafka/TbKafkaConsumerTemplate.java | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) 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 b171f2d8d8..e07be52491 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 @@ -92,10 +92,10 @@ public class DefaultTbQueueRequestTemplate responses = responseTemplate.poll(pollInterval); - if (responses.size() > 0) { - log.trace("Polling responses completed, consumer records count [{}]", responses.size()); - } + final int pendingRequestsCount = pendingRequests.size(); + log.trace("Starting template pool topic {}, for pendingRequests {}", responseTemplate.getTopic(), pendingRequestsCount); + List responses = responseTemplate.poll(pollInterval); //poll js responses + log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}]", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); responses.forEach(response -> { byte[] requestIdHeader = response.getHeaders().get(REQUEST_ID_HEADER); UUID requestId; 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 fe4300a421..816a732d41 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 @@ -21,6 +21,7 @@ import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.springframework.util.StopWatch; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.common.AbstractTbQueueConsumerTemplate; @@ -82,7 +83,16 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue @Override protected List> doPoll(long durationInMillis) { + StopWatch stopWatch = new StopWatch(); + stopWatch.start(); + + log.trace("poll topic {} maxDuration {}", getTopic(), durationInMillis); + ConsumerRecords records = consumer.poll(Duration.ofMillis(durationInMillis)); + + stopWatch.stop(); + log.trace("poll topic {} took {}ms", getTopic(), stopWatch.getTotalTimeMillis()); + if (records.isEmpty()) { return Collections.emptyList(); } else { From 67c9025a0625270f02626421d5a47d6c6c79ee3c Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 5 May 2021 18:01:52 +0300 Subject: [PATCH 396/476] improved logs for timeouts for DefaultTbQueueRequestTemplate --- .../server/queue/common/DefaultTbQueueRequestTemplate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 e07be52491..084b43f4b6 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 @@ -121,7 +121,7 @@ public class DefaultTbQueueRequestTemplate staleRequest = pendingRequests.remove(key); if (staleRequest != null) { - log.trace("[{}] Request timeout detected, expTime [{}], tickTs [{}]", key, staleRequest.expTime, tickTs); + log.info("[{}] Request timeout detected, expTime [{}], tickTs [{}]", key, staleRequest.expTime, tickTs); staleRequest.future.setException(new TimeoutException()); } } @@ -129,7 +129,7 @@ public class DefaultTbQueueRequestTemplate Date: Wed, 5 May 2021 20:10:51 +0300 Subject: [PATCH 397/476] refactored for tests DefaultTbQueueRequestTemplate class --- .../common/DefaultTbQueueRequestTemplate.java | 112 ++++++++++-------- 1 file changed, 64 insertions(+), 48 deletions(-) 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 084b43f4b6..2c779d5d5a 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 @@ -85,59 +85,75 @@ public class DefaultTbQueueRequestTemplate { - long nextCleanupMs = 0L; - while (!stopped) { - try { - final int pendingRequestsCount = pendingRequests.size(); - log.trace("Starting template pool topic {}, for pendingRequests {}", responseTemplate.getTopic(), pendingRequestsCount); - List responses = responseTemplate.poll(pollInterval); //poll js responses - log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}]", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); - responses.forEach(response -> { - byte[] requestIdHeader = response.getHeaders().get(REQUEST_ID_HEADER); - UUID requestId; - if (requestIdHeader == null) { - log.error("[{}] Missing requestId in header and body", response); - } else { - requestId = bytesToUuid(requestIdHeader); - log.trace("[{}] Response received: {}", requestId, response); - ResponseMetaData expectedResponse = pendingRequests.remove(requestId); - if (expectedResponse == null) { - log.trace("[{}] Invalid or stale request", requestId); - } else { - expectedResponse.future.set(response); + executor.submit(this::fetchAndProcessResponses); + } + + void fetchAndProcessResponses() { + long nextCleanupMs = 0L; + while (!stopped) { + try { + final int pendingRequestsCount = pendingRequests.size(); + log.trace("Starting template pool topic {}, for pendingRequests {}", responseTemplate.getTopic(), pendingRequestsCount); + List responses = doPoll(); //poll js responses + log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}]", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); + responses.forEach(this::processResponse); + responseTemplate.commit(); + tickTs = System.currentTimeMillis(); + tickSize = pendingRequests.size(); + if (nextCleanupMs < tickTs) { + //cleanup; + pendingRequests.forEach((key, value) -> { + if (value.expTime < tickTs) { + ResponseMetaData staleRequest = pendingRequests.remove(key); + if (staleRequest != null) { + setTimeoutException(key, staleRequest); } } }); - responseTemplate.commit(); - tickTs = System.currentTimeMillis(); - tickSize = pendingRequests.size(); - if (nextCleanupMs < tickTs) { - //cleanup; - pendingRequests.forEach((key, value) -> { - if (value.expTime < tickTs) { - ResponseMetaData staleRequest = pendingRequests.remove(key); - if (staleRequest != null) { - log.info("[{}] Request timeout detected, expTime [{}], tickTs [{}]", key, staleRequest.expTime, tickTs); - staleRequest.future.setException(new TimeoutException()); - } - } - }); - nextCleanupMs = tickTs + maxRequestTimeout; - } - } catch (Throwable e) { - log.warn("Failed to obtain responses from queue. Going to sleep " + pollInterval + "ms", e); - try { - Thread.sleep(pollInterval); - } catch (InterruptedException e2) { - log.trace("Failed to wait until the server has capacity to handle new responses", e2); - } + nextCleanupMs = tickTs + maxRequestTimeout; } + } catch (Throwable e) { + log.warn("Failed to obtain responses from queue. Going to sleep " + pollInterval + "ms", e); + sleep(); } - }); + } + } + + List doPoll() { + return responseTemplate.poll(pollInterval); + } + + void sleep() { + try { + Thread.sleep(pollInterval); + } catch (InterruptedException e2) { + log.trace("Failed to wait until the server has capacity to handle new responses", e2); + } + } + + void setTimeoutException(UUID key, ResponseMetaData staleRequest) { + log.info("[{}] Request timeout detected, expTime [{}], tickTs [{}]", key, staleRequest.expTime, tickTs); + staleRequest.future.setException(new TimeoutException()); + } + + void processResponse(Response response) { + byte[] requestIdHeader = response.getHeaders().get(REQUEST_ID_HEADER); + UUID requestId; + if (requestIdHeader == null) { + log.error("[{}] Missing requestId in header and body", response); + } else { + requestId = bytesToUuid(requestIdHeader); + log.trace("[{}] Response received: {}", requestId, response); + ResponseMetaData expectedResponse = pendingRequests.remove(requestId); + if (expectedResponse == null) { + log.warn("[{}] Invalid or stale request, response: {}", requestId, response); + } else { + expectedResponse.future.set(response); + } + } } @Override @@ -174,7 +190,7 @@ public class DefaultTbQueueRequestTemplate future = SettableFuture.create(); ResponseMetaData responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future); pendingRequests.putIfAbsent(requestId, responseMetaData); - log.trace("[{}] Sending request, key [{}], expTime [{}]", requestId, request.getKey(), responseMetaData.expTime); + log.trace("[{}] Sending request, key [{}], expTime [{}], request {}", requestId, request.getKey(), responseMetaData.expTime, request); if (messagesStats != null) { messagesStats.incrementTotal(); } @@ -184,7 +200,7 @@ public class DefaultTbQueueRequestTemplate Date: Thu, 6 May 2021 11:48:42 +0300 Subject: [PATCH 398/476] test: added init stop test for DefaultTbQueueRequestTemplate --- .../common/DefaultTbQueueRequestTemplate.java | 22 ++-- .../DefaultTbQueueRequestTemplateTest.java | 113 ++++++++++++++++++ 2 files changed, 123 insertions(+), 12 deletions(-) create mode 100644 common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java 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 2c779d5d5a..a51e143e26 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 @@ -31,6 +31,7 @@ import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.common.stats.MessagesStats; +import javax.annotation.Nullable; import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -47,14 +48,14 @@ public class DefaultTbQueueRequestTemplate requestTemplate; private final TbQueueConsumer responseTemplate; private final ConcurrentMap> pendingRequests; - private final boolean internalExecutor; + final boolean internalExecutor; private final ExecutorService executor; private final long maxRequestTimeout; private final long maxPendingRequests; private final long pollInterval; - private volatile long tickTs = 0L; - private volatile long tickSize = 0L; - private volatile boolean stopped = false; + volatile long tickTs = 0L; + volatile long tickSize = 0L; + volatile boolean stopped = false; private MessagesStats messagesStats; @@ -65,7 +66,7 @@ public class DefaultTbQueueRequestTemplate requestTemplate; + @Mock + TbQueueConsumer responseTemplate; + + long maxRequestTimeout = 20; + long maxPendingRequests = 10000; + long pollInterval = 25; + String topic = "js-responses-tb-node-0"; + + DefaultTbQueueRequestTemplate inst; + + @Before + public void setUp() throws Exception { + willReturn(topic).given(responseTemplate).getTopic(); + inst = new DefaultTbQueueRequestTemplate( + queueAdmin, requestTemplate, responseTemplate, + maxRequestTimeout, maxPendingRequests, pollInterval, executor); + + } + + @Test + public void givenExternalExecutor_whenStop_thenDoNotShutdownExecutor() { + inst = Mockito.spy(inst); + willDoNothing().given(inst).fetchAndProcessResponses(); + Assert.assertFalse(inst.stopped); + + inst.init(); + Assert.assertNotEquals(inst.tickTs, 0); + Assert.assertFalse(inst.internalExecutor); + verify(queueAdmin, times(1)).createTopicIfNotExists(topic); + verify(requestTemplate, times(1)).init(); + verify(responseTemplate, times(1)).subscribe(); + verify(executor, times(1)).submit(any(Runnable.class)); + + inst.stop(); + Assert.assertTrue(inst.stopped); + verify(responseTemplate, times(1)).unsubscribe(); + verify(requestTemplate, times(1)).stop(); + verify(executor, never()).shutdownNow(); + } + + @Test + public void fetchAndProcessResponses() { + inst.init(); + + inst.stop(); + } +} \ No newline at end of file From 872717828cc0e9338f6d1e986abcb67a56e0d06f Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 6 May 2021 14:54:27 +0300 Subject: [PATCH 399/476] test: refactored and added mainLoop test DefaultTbQueueRequestTemplate --- .../DefaultTbQueueRequestTemplateTest.java | 76 ++++++++++++++----- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java index 769b66fcd4..413b90c5b9 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java @@ -31,27 +31,27 @@ package org.thingsboard.server.queue.common; import org.junit.After; -import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.BDDMockito; import org.mockito.Mock; -import org.mockito.Mockito; -import org.mockito.internal.util.reflection.Whitebox; import org.mockito.runners.MockitoJUnitRunner; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.TbQueueProducer; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; import static org.junit.Assert.*; +import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Matchers.any; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -61,16 +61,17 @@ public class DefaultTbQueueRequestTemplateTest { @Mock TbQueueAdmin queueAdmin; @Mock - ExecutorService executor; - @Mock TbQueueProducer requestTemplate; @Mock TbQueueConsumer responseTemplate; + @Mock + ExecutorService executorMock; + ExecutorService executor; + String topic = "js-responses-tb-node-0"; long maxRequestTimeout = 20; long maxPendingRequests = 10000; long pollInterval = 25; - String topic = "js-responses-tb-node-0"; DefaultTbQueueRequestTemplate inst; @@ -79,35 +80,70 @@ public class DefaultTbQueueRequestTemplateTest { willReturn(topic).given(responseTemplate).getTopic(); inst = new DefaultTbQueueRequestTemplate( queueAdmin, requestTemplate, responseTemplate, - maxRequestTimeout, maxPendingRequests, pollInterval, executor); + maxRequestTimeout, maxPendingRequests, pollInterval, executorMock); + + } + @After + public void tearDown() throws Exception { + if (executor != null) { + executor.shutdownNow(); + } } @Test - public void givenExternalExecutor_whenStop_thenDoNotShutdownExecutor() { - inst = Mockito.spy(inst); - willDoNothing().given(inst).fetchAndProcessResponses(); - Assert.assertFalse(inst.stopped); + public void givenInstance_whenVerifyInitialParameters_thenOK() { + assertEquals(maxPendingRequests, inst.maxPendingRequests); + assertEquals(maxRequestTimeout, inst.maxRequestTimeout); + assertEquals(pollInterval, inst.pollInterval); + assertEquals(executorMock, inst.executor); + assertFalse(inst.stopped); + assertFalse(inst.internalExecutor); + } + + @Test + public void givenExternalExecutor_whenInitStop_thenOK() { + inst = spy(inst); + willDoNothing().given(inst).mainLoop(); inst.init(); - Assert.assertNotEquals(inst.tickTs, 0); - Assert.assertFalse(inst.internalExecutor); + assertNotEquals(0, inst.tickTs); + assertEquals(0, inst.nextCleanupMs); verify(queueAdmin, times(1)).createTopicIfNotExists(topic); verify(requestTemplate, times(1)).init(); verify(responseTemplate, times(1)).subscribe(); - verify(executor, times(1)).submit(any(Runnable.class)); + verify(executorMock, times(1)).submit(any(Runnable.class)); inst.stop(); - Assert.assertTrue(inst.stopped); + assertTrue(inst.stopped); verify(responseTemplate, times(1)).unsubscribe(); verify(requestTemplate, times(1)).stop(); - verify(executor, never()).shutdownNow(); + verify(executorMock, never()).shutdownNow(); } @Test - public void fetchAndProcessResponses() { - inst.init(); + public void givenMainLoop_whenLoopFewTimes_thenVerifyInvocationCount() throws InterruptedException { + inst = spy(inst); + executor = inst.createExecutor(); + CountDownLatch latch = new CountDownLatch(5); + willDoNothing().given(inst).sleep(); + willAnswer(invocation -> { + if (latch.getCount() == 1) { + inst.stop(); //stop the loop in natural way + } + if (latch.getCount() == 3 || latch.getCount() == 4) { + latch.countDown(); + throw new RuntimeException("test catch block"); + } + latch.countDown(); + return null; + }).given(inst).fetchAndProcessResponses(); - inst.stop(); + executor.submit(inst::mainLoop); + latch.await(10, TimeUnit.SECONDS); + + verify(inst, times(5)).fetchAndProcessResponses(); + verify(inst, times(2)).sleep(); } + } \ No newline at end of file From d5fffa5002528db63302749b354aff8cd488ce5d Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 6 May 2021 15:50:07 +0300 Subject: [PATCH 400/476] test: added failed test case with overflow maxPendingRequests for DefaultTbQueueRequestTemplate.send(). another refactoring for easy mocking. --- .../DefaultTbQueueRequestTemplateTest.java | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java index 413b90c5b9..ef9d0835d2 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java @@ -39,17 +39,22 @@ import org.mockito.runners.MockitoJUnitRunner; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsg; +import org.thingsboard.server.queue.TbQueueMsgHeaders; import org.thingsboard.server.queue.TbQueueProducer; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -70,7 +75,7 @@ public class DefaultTbQueueRequestTemplateTest { ExecutorService executor; String topic = "js-responses-tb-node-0"; long maxRequestTimeout = 20; - long maxPendingRequests = 10000; + long maxPendingRequests = 32; long pollInterval = 25; DefaultTbQueueRequestTemplate inst; @@ -78,9 +83,9 @@ public class DefaultTbQueueRequestTemplateTest { @Before public void setUp() throws Exception { willReturn(topic).given(responseTemplate).getTopic(); - inst = new DefaultTbQueueRequestTemplate( + inst = spy(new DefaultTbQueueRequestTemplate( queueAdmin, requestTemplate, responseTemplate, - maxRequestTimeout, maxPendingRequests, pollInterval, executorMock); + maxRequestTimeout, maxPendingRequests, pollInterval, executorMock)); } @@ -103,7 +108,6 @@ public class DefaultTbQueueRequestTemplateTest { @Test public void givenExternalExecutor_whenInitStop_thenOK() { - inst = spy(inst); willDoNothing().given(inst).mainLoop(); inst.init(); @@ -123,7 +127,6 @@ public class DefaultTbQueueRequestTemplateTest { @Test public void givenMainLoop_whenLoopFewTimes_thenVerifyInvocationCount() throws InterruptedException { - inst = spy(inst); executor = inst.createExecutor(); CountDownLatch latch = new CountDownLatch(5); willDoNothing().given(inst).sleep(); @@ -146,4 +149,42 @@ public class DefaultTbQueueRequestTemplateTest { verify(inst, times(2)).sleep(); } + @Test + public void givenMessages_whenSend_thenOK() { + willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any()); + inst.init(); + int msgCount = 10; + for (int i = 0; i < msgCount; i++) { + inst.send(getRequestMsgMock()); + } + assertEquals(msgCount, inst.pendingRequests.size()); + verify(inst, times(msgCount)).sendToRequestTemplate(any(), any(), any(), any()); + } + + @Test + public void givenMessagesOverMaxPendingRequests_whenSend_thenImmediateFailedFutureForTheOfRequests() { + willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any()); + inst.init(); + assertEquals(0, inst.tickSize); + int msgOverflowCount = 10; + for (int i = 0; i < inst.maxPendingRequests; i++) { + assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only + } + for (int i = 0; i < msgOverflowCount; i++) { + assertTrue("max pending requests overflow", inst.send(getRequestMsgMock()).isDone()); //overflow, immediate failed future + } + assertEquals(inst.maxPendingRequests, inst.pendingRequests.size()); + verify(inst, times((int) inst.maxPendingRequests)).sendToRequestTemplate(any(), any(), any(), any()); + } + + @Test + public void givenNothing_whenFetchAndProcessResponsesWithTimeout_thenFail() { + + } + + TbQueueMsg getRequestMsgMock() { + TbQueueMsg requestMsg = mock(TbQueueMsg.class); + willReturn(mock(TbQueueMsgHeaders.class)).given(requestMsg).getHeaders(); + return requestMsg; + } } \ No newline at end of file From 9666986156a0005f2d934646d625b29c02aa091f Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 10:04:41 +0300 Subject: [PATCH 401/476] test: added failed test for FetchAndProcessResponses when request removed as staled too early DefaultTbQueueRequestTemplate --- .../common/DefaultTbQueueRequestTemplate.java | 131 ++++++++++++------ .../DefaultTbQueueRequestTemplateTest.java | 60 ++++++-- 2 files changed, 138 insertions(+), 53 deletions(-) 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 a51e143e26..bfda553538 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 @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import lombok.Builder; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; @@ -47,15 +48,16 @@ public class DefaultTbQueueRequestTemplate requestTemplate; private final TbQueueConsumer responseTemplate; - private final ConcurrentMap> pendingRequests; + final ConcurrentMap> pendingRequests; final boolean internalExecutor; - private final ExecutorService executor; - private final long maxRequestTimeout; - private final long maxPendingRequests; - private final long pollInterval; + final ExecutorService executor; + final long maxRequestTimeout; + final long maxPendingRequests; + final long pollInterval; volatile long tickTs = 0L; volatile long tickSize = 0L; volatile boolean stopped = false; + long nextCleanupMs = 0L; private MessagesStats messagesStats; @@ -75,44 +77,26 @@ public class DefaultTbQueueRequestTemplate responses = doPoll(); //poll js responses - log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}]", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); - responses.forEach(this::processResponse); - responseTemplate.commit(); - tickTs = System.currentTimeMillis(); - tickSize = pendingRequests.size(); - if (nextCleanupMs < tickTs) { - //cleanup; - pendingRequests.forEach((key, value) -> { - if (value.expTime < tickTs) { - ResponseMetaData staleRequest = pendingRequests.remove(key); - if (staleRequest != null) { - setTimeoutException(key, staleRequest); - } - } - }); - nextCleanupMs = tickTs + maxRequestTimeout; - } + fetchAndProcessResponses(); } catch (Throwable e) { log.warn("Failed to obtain responses from queue. Going to sleep " + pollInterval + "ms", e); sleep(); @@ -120,6 +104,36 @@ public class DefaultTbQueueRequestTemplate responses = doPoll(); //poll js responses + //if (responses.size() > 0) { + log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}]", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); + //} + responses.forEach(this::processResponse); //this can take a long time + responseTemplate.commit(); + tickTs = getCurrentTime(); + tickSize = pendingRequests.size(); + if (nextCleanupMs < tickTs) { + //cleanup; + pendingRequests.forEach((key, value) -> { + if (value.expTime < tickTs) { + ResponseMetaData staleRequest = pendingRequests.remove(key); + if (staleRequest != null) { + setTimeoutException(key, staleRequest, tickTs); + } + } + }); + setupNextCleanup(); + } + } + + void setupNextCleanup() { + nextCleanupMs = tickTs + maxRequestTimeout; + log.info("setupNextCleanup {}", nextCleanupMs); + } + List doPoll() { return responseTemplate.poll(pollInterval); } @@ -132,8 +146,13 @@ public class DefaultTbQueueRequestTemplate staleRequest) { - log.info("[{}] Request timeout detected, expTime [{}], tickTs [{}]", key, staleRequest.expTime, tickTs); + void setTimeoutException(UUID key, ResponseMetaData staleRequest, long tickTs) { + if (tickTs >= staleRequest.getSubmitTime() + staleRequest.getTimeout()) { + log.info("Request timeout detected, tickTs [{}], {}, key [{}]", tickTs, staleRequest, key); + } else { + log.error("Request timeout detected, tickTs [{}], {}, key [{}]", tickTs, staleRequest, key); + } + staleRequest.future.setException(new TimeoutException()); } @@ -144,10 +163,10 @@ public class DefaultTbQueueRequestTemplate expectedResponse = pendingRequests.remove(requestId); if (expectedResponse == null) { - log.warn("[{}] Invalid or stale request, response: {}", requestId, response); + log.warn("[{}] Invalid or stale request, response: {}", requestId, String.valueOf(response).replace("\n", " ")); } else { expectedResponse.future.set(response); } @@ -184,11 +203,22 @@ public class DefaultTbQueueRequestTemplate future = SettableFuture.create(); - ResponseMetaData responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future); + ResponseMetaData responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future, currentTime, maxRequestTimeout); + log.info("pending {}", responseMetaData); pendingRequests.putIfAbsent(requestId, responseMetaData); - log.trace("[{}] Sending request, key [{}], expTime [{}], request {}", requestId, request.getKey(), responseMetaData.expTime, request); + sendToRequestTemplate(request, requestId, future, responseMetaData); + return future; + } + + long getCurrentTime() { + return System.currentTimeMillis(); + } + + void sendToRequestTemplate(Request request, UUID requestId, SettableFuture future, ResponseMetaData responseMetaData) { + log.trace("[{}] Sending request, key [{}], expTime [{}], request {}", requestId, request.getKey(), responseMetaData.expTime, String.valueOf(request).replace("\n", " ")); if (messagesStats != null) { messagesStats.incrementTotal(); } @@ -198,7 +228,7 @@ public class DefaultTbQueueRequestTemplate { + @Getter + static class ResponseMetaData { + private final long submitTime; + private final long timeout; private final long expTime; private final SettableFuture future; - ResponseMetaData(long ts, SettableFuture future) { + ResponseMetaData(long ts, SettableFuture future, long submitTime, long timeout) { + this.submitTime = submitTime; + this.timeout = timeout; this.expTime = ts; this.future = future; } + + @Override + public String toString() { + return "ResponseMetaData{" + + "submitTime=" + submitTime + + ", calculatedExpTime=" + (submitTime + timeout) + + ", expTime=" + expTime + + ", deltaMs=" + (expTime - submitTime) + + ", future=" + future + + '}'; + } } } diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java index ef9d0835d2..715b4eab21 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java @@ -29,23 +29,29 @@ * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. */ package org.thingsboard.server.queue.common; - +import lombok.extern.slf4j.Slf4j; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsg; -import org.thingsboard.server.queue.TbQueueMsgHeaders; import org.thingsboard.server.queue.TbQueueProducer; +import java.util.Collections; +import java.util.List; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; @@ -54,12 +60,17 @@ import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Matchers.any; +import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.hamcrest.MatcherAssert.assertThat; + +@Slf4j @RunWith(MockitoJUnitRunner.class) public class DefaultTbQueueRequestTemplateTest { @@ -74,9 +85,9 @@ public class DefaultTbQueueRequestTemplateTest { ExecutorService executor; String topic = "js-responses-tb-node-0"; - long maxRequestTimeout = 20; + long maxRequestTimeout = 10; long maxPendingRequests = 32; - long pollInterval = 25; + long pollInterval = 5; DefaultTbQueueRequestTemplate inst; @@ -171,20 +182,49 @@ public class DefaultTbQueueRequestTemplateTest { assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only } for (int i = 0; i < msgOverflowCount; i++) { - assertTrue("max pending requests overflow", inst.send(getRequestMsgMock()).isDone()); //overflow, immediate failed future + assertFalse("max pending requests overflow", inst.send(getRequestMsgMock()).isDone()); //overflow, immediate failed future } - assertEquals(inst.maxPendingRequests, inst.pendingRequests.size()); + assertThat(inst.pendingRequests.size(), equalTo(inst.maxPendingRequests)); verify(inst, times((int) inst.maxPendingRequests)).sendToRequestTemplate(any(), any(), any(), any()); } @Test - public void givenNothing_whenFetchAndProcessResponsesWithTimeout_thenFail() { + public void givenNothing_whenSendAndFetchAndProcessResponsesWithTimeout_thenFail() { + //given + AtomicLong currentTime = new AtomicLong(); + willAnswer(x -> { + log.info("currentTime={}", currentTime.get()); + return currentTime.get(); + }).given(inst).getCurrentTime(); + inst.init(); + inst.setupNextCleanup(); + willReturn(Collections.emptyList()).given(inst).doPoll(); + willDoNothing().given(inst).processResponse(any()); + + //when + for (int i = 0; i <= inst.maxRequestTimeout*2; i++) { + currentTime.incrementAndGet(); + assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only + if (i % (inst.maxRequestTimeout * 3 / 2) == 0) { + inst.fetchAndProcessResponses(); + } + } + + //then + ArgumentCaptor argumentCaptorResp = ArgumentCaptor.forClass(DefaultTbQueueRequestTemplate.ResponseMetaData.class); + ArgumentCaptor argumentCaptorUUID = ArgumentCaptor.forClass(UUID.class); + ArgumentCaptor argumentCaptorLong = ArgumentCaptor.forClass(Long.class); + verify(inst, atLeastOnce()).setTimeoutException(argumentCaptorUUID.capture(), argumentCaptorResp.capture(), argumentCaptorLong.capture()); + + List responseMetaDataList = argumentCaptorResp.getAllValues(); + List tickTsList = argumentCaptorLong.getAllValues(); + for (int i = 0; i < responseMetaDataList.size(); i++) { + assertThat("tickTs >= calculatedExpTime", tickTsList.get(i), greaterThanOrEqualTo(responseMetaDataList.get(i).getSubmitTime() + responseMetaDataList.get(i).getTimeout())); + } } TbQueueMsg getRequestMsgMock() { - TbQueueMsg requestMsg = mock(TbQueueMsg.class); - willReturn(mock(TbQueueMsgHeaders.class)).given(requestMsg).getHeaders(); - return requestMsg; + return mock(TbQueueMsg.class, RETURNS_DEEP_STUBS); } } \ No newline at end of file From 28235732c6140dfc9a5cb81d4c2e39b8ac217532 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 12:07:17 +0300 Subject: [PATCH 402/476] test: fixed class DefaultTbQueueRequestTemplate --- .../common/DefaultTbQueueRequestTemplate.java | 90 +++++++++++-------- .../DefaultTbQueueRequestTemplateTest.java | 37 ++++---- 2 files changed, 73 insertions(+), 54 deletions(-) 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 bfda553538..d5179385e6 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 @@ -36,10 +36,12 @@ import javax.annotation.Nullable; import java.util.List; 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.concurrent.TimeoutException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; @Slf4j public class DefaultTbQueueRequestTemplate extends AbstractTbQueueTemplate @@ -48,16 +50,15 @@ public class DefaultTbQueueRequestTemplate requestTemplate; private final TbQueueConsumer responseTemplate; - final ConcurrentMap> pendingRequests; + final ConcurrentHashMap> pendingRequests = new ConcurrentHashMap<>(); final boolean internalExecutor; final ExecutorService executor; - final long maxRequestTimeout; + final long maxRequestTimeoutNs; final long maxPendingRequests; final long pollInterval; - volatile long tickTs = 0L; - volatile long tickSize = 0L; volatile boolean stopped = false; - long nextCleanupMs = 0L; + long nextCleanupNs = 0L; + private final Lock cleanerLock = new ReentrantLock(); private MessagesStats messagesStats; @@ -72,8 +73,7 @@ public class DefaultTbQueueRequestTemplate(); - this.maxRequestTimeout = maxRequestTimeout; + this.maxRequestTimeoutNs = TimeUnit.MILLISECONDS.toNanos(maxRequestTimeout); this.maxPendingRequests = maxPendingRequests; this.pollInterval = pollInterval; this.internalExecutor = (executor == null); @@ -88,7 +88,6 @@ public class DefaultTbQueueRequestTemplate responses = doPoll(); //poll js responses //if (responses.size() > 0) { @@ -113,25 +112,36 @@ public class DefaultTbQueueRequestTemplate { - if (value.expTime < tickTs) { - ResponseMetaData staleRequest = pendingRequests.remove(key); - if (staleRequest != null) { - setTimeoutException(key, staleRequest, tickTs); + tryCleanStaleRequests(); + } + + private boolean tryCleanStaleRequests() { + if (!cleanerLock.tryLock()) { + return false; + } + try { + log.trace("tryCleanStaleRequest..."); + final long currentNs = getCurrentClockNs(); + if (nextCleanupNs < currentNs) { + pendingRequests.forEach((key, value) -> { + if (value.expTime < currentNs) { + ResponseMetaData staleRequest = pendingRequests.remove(key); + if (staleRequest != null) { + setTimeoutException(key, staleRequest, currentNs); + } } - } - }); - setupNextCleanup(); + }); + setupNextCleanup(); + } + } finally { + cleanerLock.unlock(); } + return true; } void setupNextCleanup() { - nextCleanupMs = tickTs + maxRequestTimeout; - log.info("setupNextCleanup {}", nextCleanupMs); + nextCleanupNs = getCurrentClockNs() + maxRequestTimeoutNs; + log.info("setupNextCleanup {}", nextCleanupNs); } List doPoll() { @@ -146,11 +156,11 @@ public class DefaultTbQueueRequestTemplate staleRequest, long tickTs) { - if (tickTs >= staleRequest.getSubmitTime() + staleRequest.getTimeout()) { - log.info("Request timeout detected, tickTs [{}], {}, key [{}]", tickTs, staleRequest, key); + void setTimeoutException(UUID key, ResponseMetaData staleRequest, long currentNs) { + if (currentNs >= staleRequest.getSubmitTime() + staleRequest.getTimeout()) { + log.info("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key); } else { - log.error("Request timeout detected, tickTs [{}], {}, key [{}]", tickTs, staleRequest, key); + log.error("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key); } staleRequest.future.setException(new TimeoutException()); @@ -197,23 +207,31 @@ public class DefaultTbQueueRequestTemplate send(Request request) { - if (tickSize > maxPendingRequests) { + if (pendingRequests.mappingCount() >= maxPendingRequests) { + log.warn("Pending request map is full [{}]! Consider to increase maxPendingRequests or increase processing performance", maxPendingRequests); return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!")); } UUID requestId = UUID.randomUUID(); request.getHeaders().put(REQUEST_ID_HEADER, uuidToBytes(requestId)); request.getHeaders().put(RESPONSE_TOPIC_HEADER, stringToBytes(responseTemplate.getTopic())); - long currentTime = getCurrentTime(); - request.getHeaders().put(REQUEST_TIME, longToBytes(currentTime)); + request.getHeaders().put(REQUEST_TIME, longToBytes(getCurrentTimeMs())); + long currentClockNs = getCurrentClockNs(); SettableFuture future = SettableFuture.create(); - ResponseMetaData responseMetaData = new ResponseMetaData<>(tickTs + maxRequestTimeout, future, currentTime, maxRequestTimeout); - log.info("pending {}", responseMetaData); - pendingRequests.putIfAbsent(requestId, responseMetaData); + ResponseMetaData responseMetaData = new ResponseMetaData<>(currentClockNs + maxRequestTimeoutNs, future, currentClockNs, maxRequestTimeoutNs); + log.info("pending {}", responseMetaData); //TODO trace + if (pendingRequests.putIfAbsent(requestId, responseMetaData) != null) { + log.warn("Pending request already exists [{}]!", maxPendingRequests); + return Futures.immediateFailedFuture(new RuntimeException("Pending request already exists !" + requestId)); + } sendToRequestTemplate(request, requestId, future, responseMetaData); return future; } - long getCurrentTime() { + long getCurrentClockNs() { + return System.nanoTime(); //MONOTONIC clock instead wall clock + } + + long getCurrentTimeMs() { //Wall clock to send Ts to the an external service return System.currentTimeMillis(); } @@ -261,8 +279,8 @@ public class DefaultTbQueueRequestTemplate { log.info("currentTime={}", currentTime.get()); return currentTime.get(); - }).given(inst).getCurrentTime(); + }).given(inst).getCurrentClockNs(); inst.init(); inst.setupNextCleanup(); willReturn(Collections.emptyList()).given(inst).doPoll(); willDoNothing().given(inst).processResponse(any()); //when - for (int i = 0; i <= inst.maxRequestTimeout*2; i++) { - currentTime.incrementAndGet(); + long stepNs = TimeUnit.MILLISECONDS.toNanos(1); + for (long i = 0; i <= inst.maxRequestTimeoutNs * 2; i = i + stepNs) { + currentTime.addAndGet(stepNs); assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only - if (i % (inst.maxRequestTimeout * 3 / 2) == 0) { + if (i % (inst.maxRequestTimeoutNs * 3 / 2) == 0) { inst.fetchAndProcessResponses(); } } From 928b8f0fd9ca39fe7ebe874c7ea195153779b96d Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 12:26:44 +0300 Subject: [PATCH 403/476] test: refactored for assertThat for DefaultTbQueueRequestTemplateTest --- .../common/DefaultTbQueueRequestTemplate.java | 5 ++--- .../DefaultTbQueueRequestTemplateTest.java | 17 ++++++++--------- 2 files changed, 10 insertions(+), 12 deletions(-) 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 d5179385e6..c31dc3a899 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 @@ -158,11 +158,10 @@ public class DefaultTbQueueRequestTemplate staleRequest, long currentNs) { if (currentNs >= staleRequest.getSubmitTime() + staleRequest.getTimeout()) { - log.info("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key); + log.warn("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key); } else { log.error("Request timeout detected, currentNs [{}], {}, key [{}]", currentNs, staleRequest, key); } - staleRequest.future.setException(new TimeoutException()); } @@ -218,7 +217,7 @@ public class DefaultTbQueueRequestTemplate future = SettableFuture.create(); ResponseMetaData responseMetaData = new ResponseMetaData<>(currentClockNs + maxRequestTimeoutNs, future, currentClockNs, maxRequestTimeoutNs); - log.info("pending {}", responseMetaData); //TODO trace + log.trace("pending {}", responseMetaData); if (pendingRequests.putIfAbsent(requestId, responseMetaData) != null) { log.warn("Pending request already exists [{}]!", maxPendingRequests); return Futures.immediateFailedFuture(new RuntimeException("Pending request already exists !" + requestId)); diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java index 33dfd82989..261ff9c7be 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java @@ -87,7 +87,7 @@ public class DefaultTbQueueRequestTemplateTest { ExecutorService executor; String topic = "js-responses-tb-node-0"; long maxRequestTimeout = 10; - long maxPendingRequests = 1000; + long maxPendingRequests = 32; long pollInterval = 5; DefaultTbQueueRequestTemplate inst; @@ -124,14 +124,14 @@ public class DefaultTbQueueRequestTemplateTest { inst.init(); //assertNotEquals(0, inst.tickTs); - assertEquals(0, inst.nextCleanupNs); + assertThat(inst.nextCleanupNs, equalTo(0L)); verify(queueAdmin, times(1)).createTopicIfNotExists(topic); verify(requestTemplate, times(1)).init(); verify(responseTemplate, times(1)).subscribe(); verify(executorMock, times(1)).submit(any(Runnable.class)); inst.stop(); - assertTrue(inst.stopped); + assertThat(inst.stopped, is(true)); verify(responseTemplate, times(1)).unsubscribe(); verify(requestTemplate, times(1)).stop(); verify(executorMock, never()).shutdownNow(); @@ -165,11 +165,11 @@ public class DefaultTbQueueRequestTemplateTest { public void givenMessages_whenSend_thenOK() { willDoNothing().given(inst).sendToRequestTemplate(any(), any(), any(), any()); inst.init(); - int msgCount = 10; + final int msgCount = 10; for (int i = 0; i < msgCount; i++) { inst.send(getRequestMsgMock()); } - assertEquals(msgCount, inst.pendingRequests.mappingCount()); + assertThat(inst.pendingRequests.mappingCount(), equalTo((long) msgCount)); verify(inst, times(msgCount)).sendToRequestTemplate(any(), any(), any(), any()); } @@ -179,10 +179,10 @@ public class DefaultTbQueueRequestTemplateTest { inst.init(); int msgOverflowCount = 10; for (int i = 0; i < inst.maxPendingRequests; i++) { - assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only + assertThat(inst.send(getRequestMsgMock()).isDone(), is(false)); //SettableFuture future - pending only } for (int i = 0; i < msgOverflowCount; i++) { - assertTrue("max pending requests overflow", inst.send(getRequestMsgMock()).isDone()); //overflow, immediate failed future + assertThat("max pending requests overflow", inst.send(getRequestMsgMock()).isDone(), is(true)); //overflow, immediate failed future } assertThat(inst.pendingRequests.mappingCount(), equalTo(inst.maxPendingRequests)); verify(inst, times((int) inst.maxPendingRequests)).sendToRequestTemplate(any(), any(), any(), any()); @@ -205,7 +205,7 @@ public class DefaultTbQueueRequestTemplateTest { long stepNs = TimeUnit.MILLISECONDS.toNanos(1); for (long i = 0; i <= inst.maxRequestTimeoutNs * 2; i = i + stepNs) { currentTime.addAndGet(stepNs); - assertFalse(inst.send(getRequestMsgMock()).isDone()); //SettableFuture future - pending only + assertThat(inst.send(getRequestMsgMock()).isDone(), is(false)); //SettableFuture future - pending only if (i % (inst.maxRequestTimeoutNs * 3 / 2) == 0) { inst.fetchAndProcessResponses(); } @@ -222,7 +222,6 @@ public class DefaultTbQueueRequestTemplateTest { for (int i = 0; i < responseMetaDataList.size(); i++) { assertThat("tickTs >= calculatedExpTime", tickTsList.get(i), greaterThanOrEqualTo(responseMetaDataList.get(i).getSubmitTime() + responseMetaDataList.get(i).getTimeout())); } - } TbQueueMsg getRequestMsgMock() { From 1e066f21569aa7824177a6080577ac83d29ef2a7 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 12:41:50 +0300 Subject: [PATCH 404/476] added timeout parameter to the TbQueueRequestTemplate.send( ) --- .../server/queue/TbQueueRequestTemplate.java | 2 ++ .../common/DefaultTbQueueRequestTemplate.java | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java index 5dc89a9c26..192f8e1675 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/TbQueueRequestTemplate.java @@ -24,6 +24,8 @@ public interface TbQueueRequestTemplate send(Request request); + ListenableFuture send(Request request, long timeoutNs); + void stop(); void setMessagesStats(MessagesStats messagesStats); 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 c31dc3a899..5b03e1bc2d 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 @@ -206,6 +206,11 @@ public class DefaultTbQueueRequestTemplate send(Request request) { + return send(request, this.maxRequestTimeoutNs); + } + + @Override + public ListenableFuture send(Request request, long requestTimeoutNs) { if (pendingRequests.mappingCount() >= maxPendingRequests) { log.warn("Pending request map is full [{}]! Consider to increase maxPendingRequests or increase processing performance", maxPendingRequests); return Futures.immediateFailedFuture(new RuntimeException("Pending request map is full!")); @@ -216,7 +221,7 @@ public class DefaultTbQueueRequestTemplate future = SettableFuture.create(); - ResponseMetaData responseMetaData = new ResponseMetaData<>(currentClockNs + maxRequestTimeoutNs, future, currentClockNs, maxRequestTimeoutNs); + ResponseMetaData responseMetaData = new ResponseMetaData<>(currentClockNs + requestTimeoutNs, future, currentClockNs, requestTimeoutNs); log.trace("pending {}", responseMetaData); if (pendingRequests.putIfAbsent(requestId, responseMetaData) != null) { log.warn("Pending request already exists [{}]!", maxPendingRequests); @@ -226,11 +231,18 @@ public class DefaultTbQueueRequestTemplate Date: Fri, 7 May 2021 14:07:20 +0300 Subject: [PATCH 405/476] sleep(poolInterval) replaced with parkNanos(1) for DefaultTbQueueRequestTemplate --- .../server/actors/service/DefaultActorService.java | 10 +--------- .../queue/common/DefaultTbQueueRequestTemplate.java | 10 ++++------ 2 files changed, 5 insertions(+), 15 deletions(-) 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 7a28c205bc..6f359c00b0 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 @@ -102,15 +102,7 @@ public class DefaultActorService extends TbApplicationEventListener staleRequest, long currentNs) { From 053d1d8e41443bf1f2526b84abdb500ab2ac0c06 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 26 May 2021 12:27:14 +0300 Subject: [PATCH 406/476] kafka commit sync --- .../thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 fe4300a421..bf694e00e6 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 @@ -99,7 +99,7 @@ public class TbKafkaConsumerTemplate extends AbstractTbQue @Override protected void doCommit() { - consumer.commitAsync(); + consumer.commitSync(); } @Override From 5a0336aae21bb1ad293a035e52d1b1d65ef9aa8c Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 10:30:09 +0300 Subject: [PATCH 407/476] tests: refactored actor system Test (Thread.sleep replaced with countDownLatch) --- .../server/actors/ActorSystemTest.java | 37 ++++++++++++++----- .../server/actors/SlowCreateActor.java | 15 ++++++-- 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java b/common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java index 31fae3d7a4..c37fbb1548 100644 --- a/common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java +++ b/common/actor/src/test/java/org/thingsboard/server/actors/ActorSystemTest.java @@ -41,6 +41,7 @@ public class ActorSystemTest { public static final String ROOT_DISPATCHER = "root-dispatcher"; private static final int _100K = 100 * 1024; + public static final int TIMEOUT_AWAIT_MAX_SEC = 10; private volatile TbActorSystem actorSystem; private volatile ExecutorService submitPool; @@ -52,7 +53,7 @@ public class ActorSystemTest { parallelism = Math.max(2, cores / 2); TbActorSystemSettings settings = new TbActorSystemSettings(5, parallelism, 42); actorSystem = new DefaultTbActorSystem(settings); - submitPool = Executors.newWorkStealingPool(parallelism); + submitPool = Executors.newFixedThreadPool(parallelism); //order guaranteed } @After @@ -122,13 +123,23 @@ public class ActorSystemTest { ActorTestCtx testCtx1 = getActorTestCtx(1); ActorTestCtx testCtx2 = getActorTestCtx(1); TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID())); - submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx1))); - submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx2))); + final CountDownLatch initLatch = new CountDownLatch(1); + final CountDownLatch actorsReadyLatch = new CountDownLatch(2); + submitPool.submit(() -> { + actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx1, initLatch)); + actorsReadyLatch.countDown(); + }); + submitPool.submit(() -> { + actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx2, initLatch)); + actorsReadyLatch.countDown(); + }); + + initLatch.countDown(); //replacement for Thread.wait(500) in the SlowCreateActorCreator + Assert.assertTrue(actorsReadyLatch.await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS)); - Thread.sleep(1000); actorSystem.tell(actorId, new IntTbActorMsg(42)); - Assert.assertTrue(testCtx1.getLatch().await(1, TimeUnit.SECONDS)); + Assert.assertTrue(testCtx1.getLatch().await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS)); Assert.assertFalse(testCtx2.getLatch().await(1, TimeUnit.SECONDS)); } @@ -137,13 +148,21 @@ public class ActorSystemTest { actorSystem.createDispatcher(ROOT_DISPATCHER, Executors.newWorkStealingPool(parallelism)); ActorTestCtx testCtx = getActorTestCtx(1); TbActorId actorId = new TbEntityActorId(new DeviceId(UUID.randomUUID())); - for (int i = 0; i < 1000; i++) { - submitPool.submit(() -> actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx))); + final int actorsCount = 1000; + final CountDownLatch initLatch = new CountDownLatch(1); + final CountDownLatch actorsReadyLatch = new CountDownLatch(actorsCount); + for (int i = 0; i < actorsCount; i++) { + submitPool.submit(() -> { + actorSystem.createRootActor(ROOT_DISPATCHER, new SlowCreateActor.SlowCreateActorCreator(actorId, testCtx, initLatch)); + actorsReadyLatch.countDown(); + }); } - Thread.sleep(1000); + initLatch.countDown(); + Assert.assertTrue(actorsReadyLatch.await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS)); + actorSystem.tell(actorId, new IntTbActorMsg(42)); - Assert.assertTrue(testCtx.getLatch().await(1, TimeUnit.SECONDS)); + Assert.assertTrue(testCtx.getLatch().await(TIMEOUT_AWAIT_MAX_SEC, TimeUnit.SECONDS)); //One for creation and one for message Assert.assertEquals(2, testCtx.getInvocationCount().get()); } diff --git a/common/actor/src/test/java/org/thingsboard/server/actors/SlowCreateActor.java b/common/actor/src/test/java/org/thingsboard/server/actors/SlowCreateActor.java index 50eb00a5ca..f14fe8461e 100644 --- a/common/actor/src/test/java/org/thingsboard/server/actors/SlowCreateActor.java +++ b/common/actor/src/test/java/org/thingsboard/server/actors/SlowCreateActor.java @@ -17,13 +17,18 @@ package org.thingsboard.server.actors; import lombok.extern.slf4j.Slf4j; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + @Slf4j public class SlowCreateActor extends TestRootActor { - public SlowCreateActor(TbActorId actorId, ActorTestCtx testCtx) { + public static final int TIMEOUT_AWAIT_MAX_MS = 5000; + + public SlowCreateActor(TbActorId actorId, ActorTestCtx testCtx, CountDownLatch initLatch) { super(actorId, testCtx); try { - Thread.sleep(500); + initLatch.await(TIMEOUT_AWAIT_MAX_MS, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } @@ -34,10 +39,12 @@ public class SlowCreateActor extends TestRootActor { private final TbActorId actorId; private final ActorTestCtx testCtx; + private final CountDownLatch initLatch; - public SlowCreateActorCreator(TbActorId actorId, ActorTestCtx testCtx) { + public SlowCreateActorCreator(TbActorId actorId, ActorTestCtx testCtx, CountDownLatch initLatch) { this.actorId = actorId; this.testCtx = testCtx; + this.initLatch = initLatch; } @Override @@ -47,7 +54,7 @@ public class SlowCreateActor extends TestRootActor { @Override public TbActor createActor() { - return new SlowCreateActor(actorId, testCtx); + return new SlowCreateActor(actorId, testCtx, initLatch); } } } From 16a55b16e4f7c0f678cd10088b4f8ae94c5d5921 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 15:56:45 +0300 Subject: [PATCH 408/476] js invoke service: logs added when script going to disable due to exception (special log for TimeoutException) --- .../service/script/AbstractJsInvokeService.java | 15 ++++++++++++--- .../script/AbstractNashornJsInvokeService.java | 2 +- .../service/script/RemoteJsInvokeService.java | 7 ++++--- .../common/DefaultTbQueueRequestTemplate.java | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) 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 2f0378ae9d..b089f52e76 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 @@ -30,6 +30,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; /** @@ -84,8 +85,10 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); return doInvokeFunction(scriptId, functionName, args); } else { - return Futures.immediateFailedFuture( - new RuntimeException("Script invocation is blocked due to maximum error count " + getMaxErrors() + "!")); + String message = "Script invocation is blocked due to maximum error count " + + getMaxErrors() + ", scriptId " + scriptId + "!"; + log.warn(message); + return Futures.immediateFailedFuture(new RuntimeException(message)); } } else { return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!")); @@ -117,7 +120,13 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { protected abstract long getMaxBlacklistDuration(); - protected void onScriptExecutionError(UUID scriptId) { + protected void onScriptExecutionError(UUID scriptId, Throwable t) { + if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { + log.warn("Script has TimeoutException and will increment counter {} on disabledFunctions for id {}", //TODO remove after test + disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).get(), + scriptId); + //return; //timeout is not a good reason to disable function + } disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).incrementAndGet(); } 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 9985ac60a2..f9fd3db977 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 @@ -160,7 +160,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer return ((Invocable) engine).invokeFunction(functionName, args); } } catch (Exception e) { - onScriptExecutionError(scriptId); + onScriptExecutionError(scriptId, e); throw new ExecutionException(e); } }); diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 334a471973..b8bfeea75b 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -193,7 +193,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Override public void onFailure(Throwable t) { - onScriptExecutionError(scriptId); + onScriptExecutionError(scriptId, t); if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { queueTimeoutMsgs.incrementAndGet(); } @@ -205,9 +205,10 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { if (invokeResult.getSuccess()) { return invokeResult.getResult(); } else { - onScriptExecutionError(scriptId); + final RuntimeException e = new RuntimeException(invokeResult.getErrorDetails()); + onScriptExecutionError(scriptId, e); log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); - throw new RuntimeException(invokeResult.getErrorDetails()); + throw e; } }, callbackExecutor); } 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 f6a5b33289..7b0e7a14cd 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 @@ -109,7 +109,7 @@ public class DefaultTbQueueRequestTemplate responses = doPoll(); //poll js responses //if (responses.size() > 0) { - log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}]", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); + log.info("Completed template poll topic {}, for pendingRequests [{}], received [{}]", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); //TODO reduce verbose after test //} responses.forEach(this::processResponse); //this can take a long time responseTemplate.commit(); From 24eca345768009b674ad827937247bcdd8cba523 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 5 May 2021 08:22:38 +0300 Subject: [PATCH 409/476] new utility class tbStopWatch added to improve performance measurement experience --- common/util/pom.xml | 5 ++ .../thingsboard/common/util/TbStopWatch.java | 62 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java diff --git a/common/util/pom.xml b/common/util/pom.xml index 6f66da5790..128bc478d5 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -36,6 +36,11 @@ + + org.springframework + spring-core + ${spring.version} + com.google.guava guava diff --git a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java new file mode 100644 index 0000000000..b7dbd0052c --- /dev/null +++ b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java @@ -0,0 +1,62 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.common.util; + +import org.springframework.util.StopWatch; + +/** + * Utility method that extends Spring Framework StopWatch + * It is a MONOTONIC time stopwatch. + * It is a replacement for any measurements with a wall-clock like System.currentTimeMillis() + * It is not affected by leap second, day-light saving and wall-clock adjustments by manual or network time synchronization + * The main features is a single call for common use cases: + * - create and start: TbStopWatch sw = TbStopWatch.startNew() + * - stop and get: sw.stopAndGetTotalTimeMillis() or sw.stopAndGetLastTaskTimeMillis() + * */ +public class TbStopWatch extends StopWatch { + + public static TbStopWatch startNew(){ + TbStopWatch stopWatch = new TbStopWatch(); + stopWatch.start(); + return stopWatch; + } + + public long stopAndGetTotalTimeMillis(){ + stop(); + return getTotalTimeMillis(); + } + + public long stopAndGetLastTaskTimeMillis(){ + stop(); + return getLastTaskTimeMillis(); + } + +} From e2aa4be7415790fdf371023fe22a4c25e5fcc605 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 16:26:20 +0300 Subject: [PATCH 410/476] TbStopWatch: methods for nanos added --- .../java/org/thingsboard/common/util/TbStopWatch.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java index b7dbd0052c..4f5d1346a9 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java +++ b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java @@ -54,9 +54,19 @@ public class TbStopWatch extends StopWatch { return getTotalTimeMillis(); } + public long stopAndGetTotalTimeNanos(){ + stop(); + return getLastTaskTimeNanos(); + } + public long stopAndGetLastTaskTimeMillis(){ stop(); return getLastTaskTimeMillis(); } + public long stopAndGetLastTaskTimeNanos(){ + stop(); + return getLastTaskTimeNanos(); + } + } From 9daa43a115bd584a409ea1279ca4ef6b4b0ae5cb Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 16:34:35 +0300 Subject: [PATCH 411/476] queue request template: sleep on exception shortened according to the stopwatch. test adjusted --- .../queue/common/DefaultTbQueueRequestTemplate.java | 12 +++++++----- .../common/DefaultTbQueueRequestTemplateTest.java | 10 +++++----- 2 files changed, 12 insertions(+), 10 deletions(-) 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 7b0e7a14cd..a385ef17a8 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 @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.SettableFuture; import lombok.Builder; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.TbStopWatch; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.queue.TbQueueAdmin; @@ -95,11 +96,13 @@ public class DefaultTbQueueRequestTemplate staleRequest, long currentNs) { diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java index 261ff9c7be..e60e4071f3 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java @@ -54,13 +54,13 @@ import java.util.concurrent.atomic.AtomicLong; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.Matchers.lessThan; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.longThat; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -141,7 +141,7 @@ public class DefaultTbQueueRequestTemplateTest { public void givenMainLoop_whenLoopFewTimes_thenVerifyInvocationCount() throws InterruptedException { executor = inst.createExecutor(); CountDownLatch latch = new CountDownLatch(5); - willDoNothing().given(inst).sleep(); + willDoNothing().given(inst).sleep(anyLong()); willAnswer(invocation -> { if (latch.getCount() == 1) { inst.stop(); //stop the loop in natural way @@ -158,7 +158,7 @@ public class DefaultTbQueueRequestTemplateTest { latch.await(10, TimeUnit.SECONDS); verify(inst, times(5)).fetchAndProcessResponses(); - verify(inst, times(2)).sleep(); + verify(inst, times(2)).sleep(longThat(lessThan(TimeUnit.MILLISECONDS.toNanos(inst.pollInterval)))); } @Test From c3aec4d077220aef22f369c6d6ee692205b75a47 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 7 May 2021 15:59:42 +0300 Subject: [PATCH 412/476] revert initDispatcherExecutor --- .../server/actors/service/DefaultActorService.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 6f359c00b0..98f739a24b 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 @@ -102,7 +102,16 @@ public class DefaultActorService extends TbApplicationEventListener Date: Tue, 11 May 2021 08:01:21 +0300 Subject: [PATCH 413/476] log WARN for onScriptExecutionError to prevent silent ban (actually, system going to idle if the script is a part of a rule chain). restored TRACE level on fetchAndProcessResponses --- .../service/script/AbstractJsInvokeService.java | 17 ++++++++++------- .../common/DefaultTbQueueRequestTemplate.java | 6 ++---- 2 files changed, 12 insertions(+), 11 deletions(-) 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 b089f52e76..dc01e0d97e 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 @@ -121,13 +121,16 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { protected abstract long getMaxBlacklistDuration(); protected void onScriptExecutionError(UUID scriptId, Throwable t) { - if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { - log.warn("Script has TimeoutException and will increment counter {} on disabledFunctions for id {}", //TODO remove after test - disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).get(), - scriptId); - //return; //timeout is not a good reason to disable function - } - disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).incrementAndGet(); + DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()); + log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}", + disableListInfo.get(), scriptId, t.getClass(), t.getCause()); +// if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { +// log.warn("Script has TimeoutException and will increment counter {} on disabledFunctions for id {}", //TODO remove after test +// disableListInfo.get(), +// scriptId); +// //return; //timeout is not a good reason to disable function +// } + disableListInfo.incrementAndGet(); } private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { 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 a385ef17a8..abeb739160 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 @@ -109,11 +109,9 @@ public class DefaultTbQueueRequestTemplate responses = doPoll(); //poll js responses - //if (responses.size() > 0) { - log.info("Completed template poll topic {}, for pendingRequests [{}], received [{}]", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); //TODO reduce verbose after test - //} + log.trace("Completed template poll topic {}, for pendingRequests [{}], received [{}] responses", responseTemplate.getTopic(), pendingRequestsCount, responses.size()); responses.forEach(this::processResponse); //this can take a long time responseTemplate.commit(); tryCleanStaleRequests(); From d3e4102282b581b055ec34c0fa555711a19ae472 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 11 May 2021 08:11:15 +0300 Subject: [PATCH 414/476] removed version from pom.xml from common/util for spring-core (version already defined through the root dependency management) --- common/util/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/common/util/pom.xml b/common/util/pom.xml index 128bc478d5..8a0fa1705b 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -39,7 +39,6 @@ org.springframework spring-core - ${spring.version} com.google.guava From da65eaab473d1f89131b71b0710d0c3c53e130cf Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 11 May 2021 08:20:16 +0300 Subject: [PATCH 415/476] added spring-core for the root dependency management --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index b7d0eb71f6..5f88903559 100755 --- a/pom.xml +++ b/pom.xml @@ -1009,6 +1009,11 @@ + + org.springframework + spring-core + ${spring.version} + org.springframework.boot spring-boot-starter-web From ddfb57db588dc0e1de67dd662af49a50c2ed0cd2 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 11 May 2021 09:30:58 +0300 Subject: [PATCH 416/476] log detailed error onScriptExecutionError for debug --- .../server/service/script/AbstractJsInvokeService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 dc01e0d97e..8a6aeb399b 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 @@ -123,7 +123,8 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { protected void onScriptExecutionError(UUID scriptId, Throwable t) { DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()); log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}", - disableListInfo.get(), scriptId, t.getClass(), t.getCause()); + disableListInfo.get(), scriptId, t, t.getCause()); + log.error("onScriptExecutionError", t); // if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { // log.warn("Script has TimeoutException and will increment counter {} on disabledFunctions for id {}", //TODO remove after test // disableListInfo.get(), From 5ada6b5accd763ac35cecc562dfdbb6a83fb6775 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 17 Jun 2021 13:09:23 +0300 Subject: [PATCH 417/476] LwM2M Firmware Update Delivery Method --- .../transport/lwm2m/server/LwM2mTransportUtil.java | 3 +++ .../server/ota/DefaultLwM2MOtaUpdateService.java | 14 +++++++++++++- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 2 ++ .../lwm2m/server/ota/LwM2MOtaUpdateService.java | 2 ++ .../uplink/DefaultLwM2MUplinkMsgHandler.java | 3 +++ 5 files changed, 23 insertions(+), 1 deletion(-) 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 c9248ca264..f310eb6481 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 @@ -137,6 +137,9 @@ public class LwM2mTransportUtil { public static final String FW_STATE_ID = "/5/0/3"; // Update Result R public static final String FW_RESULT_ID = "/5/0/5"; + + public static final String FW_DELIVERY_METHOD = "/5/0/9"; + // PkgName R public static final String FW_NAME_ID = "/5/0/6"; // PkgVersion R diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 68d7d76b73..d03a666ed5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -33,6 +33,7 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; @@ -180,6 +181,12 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { } } + @Override + public void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient client, Long value) { + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setDeliveryMethod(value.intValue()); + } + @Override public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion) { @@ -211,7 +218,12 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) && response.getType().equals(OtaPackageType.FIRMWARE.name())) { UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); - var strategy = fwInfo.getStrategy(); + LwM2MFirmwareUpdateStrategy strategy; + if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == 2) { + strategy = fwInfo.getStrategy(); + } else { + strategy = fwInfo.getDeliveryMethod() == 0 ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; + } switch (strategy) { case OBJ_5_BINARY: byte[] firmwareChunk = otaPackageDataCache.get(otaPackageId.toString(), 0, 0); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index 26f7abed4f..78ae7220e2 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -41,6 +41,7 @@ public class LwM2MClientOtaInfo { private boolean currentFetchFailure; private String currentName; private String currentVersion; + private Integer deliveryMethod; //TODO: use value from device if applicable; private LwM2MFirmwareUpdateStrategy strategy; @@ -96,4 +97,5 @@ public class LwM2MClientOtaInfo { private static String getPackageId(String name, String version) { return (StringUtils.isNotEmpty(name) ? name : "") + (StringUtils.isNotEmpty(version) ? version : ""); } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java index e1bde7df8b..6386e94364 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java @@ -34,4 +34,6 @@ public interface LwM2MOtaUpdateService { void onCurrentFirmwareStateUpdate(LwM2mClient client, Long state); void onCurrentFirmwareResultUpdate(LwM2mClient client, Long result); + + void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient lwM2MClient, Long value); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 97fe273857..61c4b077d9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -109,6 +109,7 @@ import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAIL import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_3_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_DELIVERY_METHOD; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; @@ -571,6 +572,8 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { otaService.onCurrentFirmwareStateUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); } else if (path.equals(convertObjectIdToVersionedId(FW_RESULT_ID, registration))) { otaService.onCurrentFirmwareResultUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(FW_DELIVERY_METHOD, registration))) { + otaService.onCurrentFirmwareDeliveryMethodUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); } this.updateAttrTelemetry(registration, Collections.singleton(path)); } else { From 56a6e601bcf357d3e1e38358fabd6c30a8cebf21 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 17 Jun 2021 12:44:18 +0300 Subject: [PATCH 418/476] added lwm2m test for firmware --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 4 +- .../lwm2m/NoSecLwM2MIntegrationTest.java | 79 ++++++++++++++++++- .../lwm2m/X509LwM2MIntegrationTest.java | 1 - 3 files changed, 80 insertions(+), 4 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java index 6db6e24f91..ad156ca3a7 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java @@ -103,7 +103,9 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { " }\n" + " },\n" + " \"clientLwM2mSettings\": {\n" + - " \"clientOnlyObserveAfterConnect\": 1\n" + + " \"clientOnlyObserveAfterConnect\": 1,\n" + + " \"fwUpdateStrategy\": 1,\n" + + " \"swUpdateStrategy\": 1\n" + " }\n" + "}"; diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index 7d173e61d9..f54a6e3382 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -17,11 +17,14 @@ package org.thingsboard.server.transport.lwm2m; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.leshan.client.object.Security; -import org.jetbrains.annotations.NotNull; import org.junit.Assert; 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.Device; +import org.thingsboard.server.common.data.OtaPackageInfo; import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; @@ -43,6 +46,7 @@ import java.util.List; import static org.eclipse.leshan.client.object.Security.noSec; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -51,7 +55,6 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); private final String ENDPOINT = "deviceAEndpoint"; - @NotNull private Device createDevice() throws Exception { Device device = new Device(); device.setName("Device A"); @@ -74,6 +77,29 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { return device; } + private OtaPackageInfo createFirmware() throws Exception { + String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; + + OtaPackageInfo firmwareInfo = new OtaPackageInfo(); + firmwareInfo.setDeviceProfileId(deviceProfile.getId()); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("My firmware"); + firmwareInfo.setVersion("v1.0"); + + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + + MockMultipartFile testData = new MockMultipartFile("file", "filename.txt", "text/plain", new byte[]{1}); + + return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); + } + + protected OtaPackageInfo 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()), OtaPackageInfo.class); + } + @Test public void testConnectAndObserveTelemetry() throws Exception { createDeviceProfile(TRANSPORT_CONFIGURATION); @@ -111,4 +137,53 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { client.destroy(); } + @Test + public void testFirmwareUpdateWithClientWithoutFirmwareInfo() throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + + Device device = createDevice(); + + OtaPackageInfo firmware = createFirmware(); + + LwM2MTestClient client = new LwM2MTestClient(executor, ENDPOINT); + client.init(SECURITY, COAP_CONFIG); + + device.setFirmwareId(firmware.getId()); + + Thread.sleep(1000); + + device = doPost("/api/device", device, Device.class); + + Thread.sleep(1000); + + SingleEntityFilter sef = new SingleEntityFilter(); + sef.setSingleEntity(device.getId()); + LatestValueCmd latestCmd = new LatestValueCmd(); + latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "fw_state"))); + EntityDataQuery edq = new EntityDataQuery(sef, new EntityDataPageLink(1, 0, null, null), + Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + + EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null); + TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); + wrapper.setEntityDataCmds(Collections.singletonList(cmd)); + + wsClient.send(mapper.writeValueAsString(wrapper)); + wsClient.waitForReply(); + + wsClient.registerWaitForUpdate(); + + String msg = wsClient.waitForUpdate(); + + EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); + Assert.assertEquals(1, update.getCmdId()); + List eData = update.getUpdate(); + Assert.assertNotNull(eData); + Assert.assertEquals(1, eData.size()); + Assert.assertEquals(device.getId(), eData.get(0).getEntityId()); + Assert.assertNotNull(eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES)); + var tsValue = eData.get(0).getLatest().get(EntityKeyType.TIME_SERIES).get("fw_state"); + Assert.assertEquals("FAILED", tsValue.getValue()); + client.destroy(); + } + } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java index 0c13c57441..a5efff968a 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -52,7 +52,6 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { private final String endpoint = "deviceAEndpoint"; private final String serverUri = "coaps://localhost:" + port; - @NotNull private Device createDevice(X509ClientCredentials clientCredentials) throws Exception { Device device = new Device(); device.setName("Device A"); From b4a90ceab97356a4f36e77c9f3cf0048e11e3c22 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 17 Jun 2021 13:02:47 +0300 Subject: [PATCH 419/476] improved lwm2m test for firmware --- .../server/transport/TransportSqlTestSuite.java | 14 +++++++------- .../transport/lwm2m/NoSecLwM2MIntegrationTest.java | 4 ++-- .../server/ota/DefaultLwM2MOtaUpdateService.java | 4 +++- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 5 +++++ 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index 25df3bee00..d059ea1449 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ - "org.thingsboard.server.transport.*.rpc.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", - "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", - "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", - "org.thingsboard.server.transport.*.attributes.request.sql.*Test", - "org.thingsboard.server.transport.*.claim.sql.*Test", - "org.thingsboard.server.transport.*.provision.sql.*Test", +// "org.thingsboard.server.transport.*.rpc.sql.*Test", +// "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", +// "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", +// "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", +// "org.thingsboard.server.transport.*.attributes.request.sql.*Test", +// "org.thingsboard.server.transport.*.claim.sql.*Test", +// "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index f54a6e3382..e94f432f64 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -148,10 +148,10 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { LwM2MTestClient client = new LwM2MTestClient(executor, ENDPOINT); client.init(SECURITY, COAP_CONFIG); - device.setFirmwareId(firmware.getId()); - Thread.sleep(1000); + device.setFirmwareId(firmware.getId()); + device = doPost("/api/device", device, Device.class); Thread.sleep(1000); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index d03a666ed5..3f86131320 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -36,8 +36,8 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; -import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; +import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; @@ -200,6 +200,8 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { } else { startFirmwareUpdateUsingBinary(client, fwInfo); } + } else if (fwInfo.isUpdateFailed()) { + sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index 78ae7220e2..50b44f8d06 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -80,6 +80,11 @@ public class LwM2MClientOtaInfo { } } + public boolean isUpdateFailed() { + return (StringUtils.isNotEmpty(targetName) && StringUtils.isNotEmpty(targetVersion) && + (StringUtils.isEmpty(currentName) && StringUtils.isEmpty(currentVersion))); + } + public void setUpdateResult(UpdateResultFw updateResult) { this.updateResult = updateResult; switch (updateResult) { From 1d33c09127165907def60b09bca41a11b60d8d85 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 11 May 2021 16:22:10 +0300 Subject: [PATCH 420/476] JsInvokeService - log js body on script failure to investigate easy --- .../server/service/script/AbstractJsInvokeService.java | 7 +++---- .../service/script/AbstractNashornJsInvokeService.java | 2 +- .../server/service/script/RemoteJsInvokeService.java | 10 +++++----- 3 files changed, 9 insertions(+), 10 deletions(-) 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 8a6aeb399b..cae4a8e05c 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 @@ -120,11 +120,10 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { protected abstract long getMaxBlacklistDuration(); - protected void onScriptExecutionError(UUID scriptId, Throwable t) { + protected void onScriptExecutionError(UUID scriptId, Throwable t, String scriptBody) { DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()); - log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}", - disableListInfo.get(), scriptId, t, t.getCause()); - log.error("onScriptExecutionError", t); + log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}", + disableListInfo.get(), scriptId, t, t.getCause(), scriptBody); // if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { // log.warn("Script has TimeoutException and will increment counter {} on disabledFunctions for id {}", //TODO remove after test // disableListInfo.get(), 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 f9fd3db977..15a3cf1c15 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 @@ -160,7 +160,7 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer return ((Invocable) engine).invokeFunction(functionName, args); } } catch (Exception e) { - onScriptExecutionError(scriptId, e); + onScriptExecutionError(scriptId, e, functionName); throw new ExecutionException(e); } }); diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index b8bfeea75b..5b3b1f5e23 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.script; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -161,7 +160,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Override protected ListenableFuture doInvokeFunction(UUID scriptId, String functionName, Object[] args) { - String scriptBody = scriptIdToBodysMap.get(scriptId); + log.trace("doInvokeFunction js-request for uuid {} with timeout {}ms", scriptId, maxRequestsTimeout); + final String scriptBody = scriptIdToBodysMap.get(scriptId); if (scriptBody == null) { return Futures.immediateFailedFuture(new RuntimeException("No script body found for scriptId: [" + scriptId + "]!")); } @@ -170,7 +170,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { .setScriptIdLSB(scriptId.getLeastSignificantBits()) .setFunctionName(functionName) .setTimeout((int) maxRequestsTimeout) - .setScriptBody(scriptIdToBodysMap.get(scriptId)); + .setScriptBody(scriptBody); for (Object arg : args) { jsRequestBuilder.addArgs(arg.toString()); @@ -193,7 +193,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Override public void onFailure(Throwable t) { - onScriptExecutionError(scriptId, t); + onScriptExecutionError(scriptId, t, scriptBody); if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { queueTimeoutMsgs.incrementAndGet(); } @@ -206,7 +206,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { return invokeResult.getResult(); } else { final RuntimeException e = new RuntimeException(invokeResult.getErrorDetails()); - onScriptExecutionError(scriptId, e); + onScriptExecutionError(scriptId, e, scriptBody); log.debug("[{}] Failed to compile script due to [{}]: {}", scriptId, invokeResult.getErrorCode().name(), invokeResult.getErrorDetails()); throw e; } From 7e551cc92a94aef3727f5732837846c588a4805b Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 13 May 2021 20:23:24 +0300 Subject: [PATCH 421/476] log: reduced severity to trace for DefaultTbQueueRequestTemplate.setupNextCleanup() --- .../server/queue/common/DefaultTbQueueRequestTemplate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 abeb739160..7fa3ac5989 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 @@ -143,7 +143,7 @@ public class DefaultTbQueueRequestTemplate doPoll() { From ed44ac8f19e14a652978123147f9869e478c2958 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 13:27:19 +0300 Subject: [PATCH 422/476] test fixed for DefaultTbQueueRequestTemplateTest --- .../queue/common/DefaultTbQueueRequestTemplateTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java index e60e4071f3..c73be715fe 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java @@ -37,7 +37,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsg; @@ -55,12 +55,11 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThanOrEqualTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.lessThan; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willReturn; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyLong; -import static org.mockito.Matchers.longThat; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; @@ -70,6 +69,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.hamcrest.MockitoHamcrest.longThat; @Slf4j @RunWith(MockitoJUnitRunner.class) From 63c906250c39799a8b0142c529d6b21d71d52477 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 13:27:52 +0300 Subject: [PATCH 423/476] log cleanup DefaultTbQueueRequestTemplate --- .../server/queue/common/DefaultTbQueueRequestTemplate.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7fa3ac5989..7463c11df5 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 @@ -245,7 +245,7 @@ public class DefaultTbQueueRequestTemplate future, ResponseMetaData responseMetaData) { - log.trace("[{}] Sending request, key [{}], expTime [{}], request {}", requestId, request.getKey(), responseMetaData.expTime, String.valueOf(request).replace("\n", " ")); + log.trace("[{}] Sending request, key [{}], expTime [{}], request {}", requestId, request.getKey(), responseMetaData.expTime, request); if (messagesStats != null) { messagesStats.incrementTotal(); } @@ -255,7 +255,7 @@ public class DefaultTbQueueRequestTemplate Date: Thu, 17 Jun 2021 13:34:06 +0300 Subject: [PATCH 424/476] code cleanup --- .../server/actors/service/DefaultActorService.java | 1 - .../server/service/script/AbstractJsInvokeService.java | 6 ------ 2 files changed, 7 deletions(-) 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 98f739a24b..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 @@ -111,7 +111,6 @@ public class DefaultActorService extends TbApplicationEventListener new DisableListInfo()); log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}", disableListInfo.get(), scriptId, t, t.getCause(), scriptBody); -// if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { -// log.warn("Script has TimeoutException and will increment counter {} on disabledFunctions for id {}", //TODO remove after test -// disableListInfo.get(), -// scriptId); -// //return; //timeout is not a good reason to disable function -// } disableListInfo.incrementAndGet(); } From 75603e58bec47a8b30292cdaa57b03831547514f Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 13:34:54 +0300 Subject: [PATCH 425/476] TbStopWatch updated licence header for CE --- .../thingsboard/common/util/TbStopWatch.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java index 4f5d1346a9..90f58ce7f2 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java +++ b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.common.util; From aaedb9e879ccd1ad09b14d5cc717ec981f6d1927 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 13:35:20 +0300 Subject: [PATCH 426/476] DefaultTbQueueRequestTemplateTest updated licence header for CE --- .../DefaultTbQueueRequestTemplateTest.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java index c73be715fe..e50e204adb 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.common; From bec228bc83d1541ed69c92ebb77fd13bb606eb47 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 13:42:37 +0300 Subject: [PATCH 427/476] test: removed unnecessary stubbing on DefaultTbQueueRequestTemplateTest --- .../queue/common/DefaultTbQueueRequestTemplateTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java index e50e204adb..9979e9ac43 100644 --- a/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java +++ b/common/queue/src/test/java/org/thingsboard/server/queue/common/DefaultTbQueueRequestTemplateTest.java @@ -105,10 +105,7 @@ public class DefaultTbQueueRequestTemplateTest { @Test public void givenExternalExecutor_whenInitStop_thenOK() { - willDoNothing().given(inst).mainLoop(); - inst.init(); - //assertNotEquals(0, inst.tickTs); assertThat(inst.nextCleanupNs, equalTo(0L)); verify(queueAdmin, times(1)).createTopicIfNotExists(topic); verify(requestTemplate, times(1)).init(); @@ -184,7 +181,6 @@ public class DefaultTbQueueRequestTemplateTest { inst.init(); inst.setupNextCleanup(); willReturn(Collections.emptyList()).given(inst).doPoll(); - willDoNothing().given(inst).processResponse(any()); //when long stepNs = TimeUnit.MILLISECONDS.toNanos(1); From 8915b279a027b16c1d45d2e9728ee5dcf474fa7d Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 16 Jun 2021 12:40:38 +0300 Subject: [PATCH 428/476] js-script-engine-api: executeSwitch replaced with asynchronous executeSwitchAsync --- .../controller/RuleChainController.java | 2 +- .../script/RuleNodeJsScriptEngine.java | 12 ++++++--- .../rule/engine/api/ScriptEngine.java | 2 +- .../rule/engine/filter/TbJsSwitchNode.java | 27 ++++++++++++------- .../engine/filter/TbJsSwitchNodeTest.java | 2 +- 5 files changed, 29 insertions(+), 16 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 3b9af2f0f2..39692922c1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -400,7 +400,7 @@ public class RuleChainController extends BaseController { output = Boolean.toString(result); break; case "switch": - Set states = engine.executeSwitch(inMsg); + Set states = engine.executeSwitchAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); output = objectMapper.writeValueAsString(states); break; case "json": 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 c688d6978d..e9cf8d621f 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 @@ -195,9 +195,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S }, MoreExecutors.directExecutor()); } - @Override - public Set executeSwitch(TbMsg msg) throws ScriptException { - JsonNode result = executeScript(msg); + Set executeSwitchPostProcessFunction(JsonNode result) throws ScriptException { if (result.isTextual()) { return Collections.singleton(result.asText()); } else if (result.isArray()) { @@ -217,6 +215,14 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } } + @Override + public ListenableFuture> executeSwitchAsync(TbMsg msg) { + log.trace("execute switch async, msg {}", msg); + return Futures.transformAsync(executeScriptAsync(msg), + result -> Futures.immediateFuture(executeSwitchPostProcessFunction(result)), + MoreExecutors.directExecutor()); //usually runs in a callbackExecutor + } + private JsonNode executeScript(TbMsg msg) throws ScriptException { try { String[] inArgs = prepareArgs(msg); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java index a5dcf535e4..da9bd93f2b 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java @@ -35,7 +35,7 @@ public interface ScriptEngine { ListenableFuture executeFilterAsync(TbMsg msg); - Set executeSwitch(TbMsg msg) throws ScriptException; + ListenableFuture> executeSwitchAsync(TbMsg msg); JsonNode executeJson(TbMsg msg) throws ScriptException; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index f677b90158..5af4ccb29d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -15,7 +15,11 @@ */ package org.thingsboard.rule.engine.filter; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.ScriptEngine; @@ -58,17 +62,20 @@ public class TbJsSwitchNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ListeningExecutor jsExecutor = ctx.getJsExecutor(); ctx.logJsEvalRequest(); - withCallback(jsExecutor.executeAsync(() -> jsEngine.executeSwitch(msg)), - result -> { - ctx.logJsEvalResponse(); - processSwitch(ctx, msg, result); - }, - t -> { - ctx.logJsEvalFailure(); - ctx.tellFailure(msg, t); - }, ctx.getDbCallbackExecutor()); + Futures.addCallback(jsEngine.executeSwitchAsync(msg), new FutureCallback>() { + @Override + public void onSuccess(@Nullable Set result) { + ctx.logJsEvalResponse(); + processSwitch(ctx, msg, result); + } + + @Override + public void onFailure(Throwable t) { + ctx.logJsEvalFailure(); + ctx.tellFailure(msg, t); + } + }, MoreExecutors.directExecutor()); //usually runs in a callbackExecutor } private void processSwitch(TbContext ctx, TbMsg msg, Set nextRelations) { diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index cfacf4cb85..fc2e5aa041 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -73,7 +73,7 @@ public class TbJsSwitchNodeTest { TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); mockJsExecutor(); - when(scriptEngine.executeSwitch(msg)).thenReturn(Sets.newHashSet("one", "three")); + when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three"))); node.onMsg(ctx, msg); verify(ctx).getJsExecutor(); From f3757ad127cd0029e347d7b5858a99e4eab62f93 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 16 Jun 2021 18:41:18 +0300 Subject: [PATCH 429/476] js-script-engine-api: refactored executeSwitchAsync --- .../script/RuleNodeJsScriptEngine.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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 e9cf8d621f..2e6b48693a 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 @@ -18,7 +18,6 @@ package org.thingsboard.server.service.script; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; @@ -32,6 +31,7 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import javax.script.ScriptException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -145,7 +145,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S return Futures.transformAsync(executeScriptAsync(prevMsg), result -> { if (!result.isObject()) { log.warn("Wrong result type: {}", result.getNodeType()); - throw new ScriptException("Wrong result type: " + result.getNodeType()); + Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); } return Futures.immediateFuture(unbindMsg(result, prevMsg)); }, MoreExecutors.directExecutor()); @@ -195,31 +195,31 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S }, MoreExecutors.directExecutor()); } - Set executeSwitchPostProcessFunction(JsonNode result) throws ScriptException { + ListenableFuture> executeSwitchPostProcessAsyncFunction(JsonNode result) { if (result.isTextual()) { - return Collections.singleton(result.asText()); - } else if (result.isArray()) { - Set nextStates = Sets.newHashSet(); + return Futures.immediateFuture(Collections.singleton(result.asText())); + } + if (result.isArray()) { + Set nextStates = new HashSet<>(); for (JsonNode val : result) { if (!val.isTextual()) { log.warn("Wrong result type: {}", val.getNodeType()); - throw new ScriptException("Wrong result type: " + val.getNodeType()); + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + val.getNodeType())); } else { nextStates.add(val.asText()); } } - return nextStates; - } else { - log.warn("Wrong result type: {}", result.getNodeType()); - throw new ScriptException("Wrong result type: " + result.getNodeType()); + return Futures.immediateFuture(nextStates); } + log.warn("Wrong result type: {}", result.getNodeType()); + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); } @Override public ListenableFuture> executeSwitchAsync(TbMsg msg) { log.trace("execute switch async, msg {}", msg); return Futures.transformAsync(executeScriptAsync(msg), - result -> Futures.immediateFuture(executeSwitchPostProcessFunction(result)), + this::executeSwitchPostProcessAsyncFunction, MoreExecutors.directExecutor()); //usually runs in a callbackExecutor } From b397dfb518e101558790127b5e7ad9a7e7aef34f Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 16 Jun 2021 20:49:46 +0300 Subject: [PATCH 430/476] js-script-engine-api: js sync calls replaced with completely async calls (CE API only) --- .../controller/RuleChainController.java | 8 +- .../script/RuleNodeJsScriptEngine.java | 140 +++++++----------- .../rule/engine/api/ScriptEngine.java | 9 +- .../rule/engine/api/TbContext.java | 4 + .../rule/engine/action/TbLogNode.java | 30 ++-- .../rule/engine/filter/TbJsSwitchNode.java | 2 - .../engine/filter/TbJsSwitchNodeTest.java | 17 +-- 7 files changed, 82 insertions(+), 128 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 39692922c1..fbfd0d595f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -390,13 +390,13 @@ public class RuleChainController extends BaseController { TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); switch (scriptType) { case "update": - output = msgToOutput(engine.executeUpdate(inMsg)); + output = msgToOutput(engine.executeUpdateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); break; case "generate": output = msgToOutput(engine.executeGenerateAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS)); break; case "filter": - boolean result = engine.executeFilter(inMsg); + boolean result = engine.executeFilterAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); output = Boolean.toString(result); break; case "switch": @@ -404,11 +404,11 @@ public class RuleChainController extends BaseController { output = objectMapper.writeValueAsString(states); break; case "json": - JsonNode json = engine.executeJson(inMsg); + JsonNode json = engine.executeJsonAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); output = objectMapper.writeValueAsString(json); break; case "string": - output = engine.executeToString(inMsg); + output = engine.executeToStringAsync(inMsg).get(TIMEOUT, TimeUnit.SECONDS); break; default: throw new IllegalArgumentException("Unsupported script type: " + scriptType); 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 2e6b48693a..17e999a533 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 @@ -106,96 +106,77 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } } - @Override - public List executeUpdate(TbMsg msg) throws ScriptException { - JsonNode result = executeScript(msg); - if (result.isObject()) { - return Collections.singletonList(unbindMsg(result, msg)); - } else if (result.isArray()){ - List res = new ArrayList<>(result.size()); - result.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); - return res; - } else { - log.warn("Wrong result type: {}", result.getNodeType()); - throw new ScriptException("Wrong result type: " + result.getNodeType()); - } - } - @Override public ListenableFuture> executeUpdateAsync(TbMsg msg) { ListenableFuture result = executeScriptAsync(msg); - return Futures.transformAsync(result, json -> { - if (json.isObject()) { - return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); - } else if (json.isArray()){ - List res = new ArrayList<>(json.size()); - json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); - return Futures.immediateFuture(res); - } - else{ - log.warn("Wrong result type: {}", json.getNodeType()); - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); - } - }, MoreExecutors.directExecutor()); + return Futures.transformAsync(result, + json -> executeUpdateTransform(msg, json), + MoreExecutors.directExecutor()); + } + + ListenableFuture> executeUpdateTransform(TbMsg msg, JsonNode json) { + if (json.isObject()) { + return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); + } else if (json.isArray()) { + List res = new ArrayList<>(json.size()); + json.forEach(jsonObject -> res.add(unbindMsg(jsonObject, msg))); + return Futures.immediateFuture(res); + } + log.warn("Wrong result type: {}", json.getNodeType()); + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); } @Override public ListenableFuture executeGenerateAsync(TbMsg prevMsg) { - log.trace("execute generate async, prevMsg {}", prevMsg); - return Futures.transformAsync(executeScriptAsync(prevMsg), result -> { - if (!result.isObject()) { - log.warn("Wrong result type: {}", result.getNodeType()); - Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); - } - return Futures.immediateFuture(unbindMsg(result, prevMsg)); - }, MoreExecutors.directExecutor()); - + return Futures.transformAsync(executeScriptAsync(prevMsg), + result -> executeGenerateTransform(prevMsg, result), + MoreExecutors.directExecutor()); } - @Override - public JsonNode executeJson(TbMsg msg) throws ScriptException { - return executeScript(msg); + ListenableFuture executeGenerateTransform(TbMsg prevMsg, JsonNode result) { + if (!result.isObject()) { + log.warn("Wrong result type: {}", result.getNodeType()); + Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); + } + return Futures.immediateFuture(unbindMsg(result, prevMsg)); } @Override - public ListenableFuture executeJsonAsync(TbMsg msg) throws ScriptException { + public ListenableFuture executeJsonAsync(TbMsg msg) { return executeScriptAsync(msg); } @Override - public String executeToString(TbMsg msg) throws ScriptException { - JsonNode result = executeScript(msg); - if (!result.isTextual()) { - log.warn("Wrong result type: {}", result.getNodeType()); - throw new ScriptException("Wrong result type: " + result.getNodeType()); - } - return result.asText(); + public ListenableFuture executeToStringAsync(TbMsg msg) { + return Futures.transformAsync(executeScriptAsync(msg), + this::executeToStringTransform, + MoreExecutors.directExecutor()); } - @Override - public boolean executeFilter(TbMsg msg) throws ScriptException { - JsonNode result = executeScript(msg); - if (!result.isBoolean()) { - log.warn("Wrong result type: {}", result.getNodeType()); - throw new ScriptException("Wrong result type: " + result.getNodeType()); + ListenableFuture executeToStringTransform(JsonNode result) { + if (result.isTextual()) { + return Futures.immediateFuture(result.asText()); } - return result.asBoolean(); + log.warn("Wrong result type: {}", result.getNodeType()); + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); } @Override public ListenableFuture executeFilterAsync(TbMsg msg) { - ListenableFuture result = executeScriptAsync(msg); - return Futures.transformAsync(result, json -> { - if (!json.isBoolean()) { - log.warn("Wrong result type: {}", json.getNodeType()); - return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); - } else { - return Futures.immediateFuture(json.asBoolean()); - } - }, MoreExecutors.directExecutor()); + return Futures.transformAsync(executeScriptAsync(msg), + this::executeFilterTransform, + MoreExecutors.directExecutor()); + } + + ListenableFuture executeFilterTransform(JsonNode json) { + if (json.isBoolean()) { + return Futures.immediateFuture(json.asBoolean()); + } + log.warn("Wrong result type: {}", json.getNodeType()); + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); } - ListenableFuture> executeSwitchPostProcessAsyncFunction(JsonNode result) { + ListenableFuture> executeSwitchTransform(JsonNode result) { if (result.isTextual()) { return Futures.immediateFuture(Collections.singleton(result.asText())); } @@ -217,34 +198,19 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S @Override public ListenableFuture> executeSwitchAsync(TbMsg msg) { - log.trace("execute switch async, msg {}", msg); return Futures.transformAsync(executeScriptAsync(msg), - this::executeSwitchPostProcessAsyncFunction, + this::executeSwitchTransform, MoreExecutors.directExecutor()); //usually runs in a callbackExecutor } - private JsonNode executeScript(TbMsg msg) throws ScriptException { - try { - String[] inArgs = prepareArgs(msg); - 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) { - throw (ScriptException) e.getCause(); - } else if (e.getCause() instanceof RuntimeException) { - throw new ScriptException(e.getCause().getMessage()); - } else { - throw new ScriptException(e); - } - } catch (Exception e) { - throw new ScriptException(e); - } - } - - private ListenableFuture executeScriptAsync(TbMsg msg) { + ListenableFuture executeScriptAsync(TbMsg msg) { log.trace("execute script async, msg {}", msg); String[] inArgs = prepareArgs(msg); - return Futures.transformAsync(sandboxService.invokeFunction(tenantId, msg.getCustomerId(), this.scriptId, inArgs[0], inArgs[1], inArgs[2]), + return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]); + } + + ListenableFuture executeScriptAsync(CustomerId customerId, Object... args) { + return Futures.transformAsync(sandboxService.invokeFunction(tenantId, customerId, this.scriptId, args), o -> { try { return Futures.immediateFuture(mapper.readTree(o.toString())); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java index da9bd93f2b..012e135c60 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java @@ -19,14 +19,11 @@ import com.fasterxml.jackson.databind.JsonNode; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.msg.TbMsg; -import javax.script.ScriptException; import java.util.List; import java.util.Set; public interface ScriptEngine { - List executeUpdate(TbMsg msg) throws ScriptException; - ListenableFuture> executeUpdateAsync(TbMsg msg); ListenableFuture executeGenerateAsync(TbMsg prevMsg); @@ -37,11 +34,9 @@ public interface ScriptEngine { ListenableFuture> executeSwitchAsync(TbMsg msg); - JsonNode executeJson(TbMsg msg) throws ScriptException; - - ListenableFuture executeJsonAsync(TbMsg msg) throws ScriptException; + ListenableFuture executeJsonAsync(TbMsg msg); - String executeToString(TbMsg msg) throws ScriptException; + ListenableFuture executeToStringAsync(TbMsg msg); void destroy(); 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 43e3a5e329..62ed9b0fa0 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 @@ -214,6 +214,10 @@ public interface TbContext { EdgeEventService getEdgeEventService(); + /** + * Js script executors call are completely asynchronous + * */ + @Deprecated ListeningExecutor getJsExecutor(); ListeningExecutor getMailExecutor(); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java index 2b2f0d76a4..c981d5062d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java @@ -15,8 +15,11 @@ */ package org.thingsboard.rule.engine.action; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.common.util.ListeningExecutor; +import org.checkerframework.checker.nullness.qual.Nullable; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; @@ -55,18 +58,21 @@ public class TbLogNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { - ListeningExecutor jsExecutor = ctx.getJsExecutor(); ctx.logJsEvalRequest(); - withCallback(jsExecutor.executeAsync(() -> jsEngine.executeToString(msg)), - toString -> { - ctx.logJsEvalResponse(); - log.info(toString); - ctx.tellSuccess(msg); - }, - t -> { - ctx.logJsEvalResponse(); - ctx.tellFailure(msg, t); - }); + Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback() { + @Override + public void onSuccess(@Nullable String result) { + ctx.logJsEvalResponse(); + log.info(result); + ctx.tellSuccess(msg); + } + + @Override + public void onFailure(Throwable t) { + ctx.logJsEvalResponse(); + ctx.tellFailure(msg, t); + } + }, MoreExecutors.directExecutor()); //usually js responses runs on js callback executor } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index 5af4ccb29d..bc900a0f2d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -33,8 +33,6 @@ import org.thingsboard.server.common.msg.TbMsg; import java.util.Set; -import static org.thingsboard.common.util.DonAsynchron.withCallback; - @Slf4j @RuleNode( type = ComponentType.FILTER, diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index fc2e5aa041..4ac81120b0 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -64,7 +64,7 @@ public class TbJsSwitchNodeTest { private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); @Test - public void multipleRoutesAreAllowed() throws TbNodeException, ScriptException { + public void multipleRoutesAreAllowed() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("temp", "10"); @@ -72,11 +72,9 @@ public class TbJsSwitchNodeTest { String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); - mockJsExecutor(); when(scriptEngine.executeSwitchAsync(msg)).thenReturn(Futures.immediateFuture(Sets.newHashSet("one", "three"))); node.onMsg(ctx, msg); - verify(ctx).getJsExecutor(); verify(ctx).tellNext(msg, Sets.newHashSet("one", "three")); } @@ -92,19 +90,6 @@ public class TbJsSwitchNodeTest { node.init(ctx, nodeConfiguration); } - @SuppressWarnings("unchecked") - private void mockJsExecutor() { - when(ctx.getJsExecutor()).thenReturn(executor); - doAnswer((Answer>>) invocationOnMock -> { - try { - Callable task = (Callable) (invocationOnMock.getArguments())[0]; - return Futures.immediateFuture((Set) task.call()); - } catch (Throwable th) { - return Futures.immediateFailedFuture(th); - } - }).when(executor).executeAsync(ArgumentMatchers.any(Callable.class)); - } - private void verifyError(TbMsg msg, String message, Class expectedClass) { ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); verify(ctx).tellFailure(same(msg), captor.capture()); From 966b0923e7e293c0bd5fb6aa3f98d43aa9dc93de Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 14:12:33 +0300 Subject: [PATCH 431/476] js-script-engine-api: fixed CE merge --- .../server/service/script/RuleNodeJsScriptEngine.java | 1 + .../main/java/org/thingsboard/rule/engine/api/ScriptEngine.java | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) 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 17e999a533..4ab81702d5 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 @@ -23,6 +23,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +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.TbMsg; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java index 012e135c60..7ff63319cd 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/ScriptEngine.java @@ -28,8 +28,6 @@ public interface ScriptEngine { ListenableFuture executeGenerateAsync(TbMsg prevMsg); - boolean executeFilter(TbMsg msg) throws ScriptException; - ListenableFuture executeFilterAsync(TbMsg msg); ListenableFuture> executeSwitchAsync(TbMsg msg); From 6a63b6e31dd02b9b63178f8bdf77be2cc8740b78 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Thu, 17 Jun 2021 14:16:46 +0300 Subject: [PATCH 432/476] fix to entity node level --- .../widget/lib/entities-hierarchy-widget.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts index 5fb11d025b..03d97c50fb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts @@ -443,7 +443,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O const dataPageData = subscription.dataPages[0]; const childNodes: HierarchyNavTreeNode[] = []; datasourcesPageData.data.forEach((childDatasource, index) => { - childNodes.push(this.datasourceToNode(childDatasource as HierarchyNodeDatasource, dataPageData.data[index])); + childNodes.push(this.datasourceToNode(childDatasource as HierarchyNodeDatasource, dataPageData.data[index], nodeCtx)); }); nodeCtx.childrenNodesLoaded = true; childrenNodesLoadCb(this.prepareNodes(childNodes)); From 29910fad8f6634e4fa7cfc1db2cf74d2e723f7d8 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 17 Jun 2021 13:34:54 +0300 Subject: [PATCH 433/476] TbStopWatch updated licence header for CE --- .../thingsboard/common/util/TbStopWatch.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java index b7dbd0052c..155ffb5360 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java +++ b/common/util/src/main/java/org/thingsboard/common/util/TbStopWatch.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.common.util; From 75801045cfcdda85b90973d7e3079b22b747de52 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 17 Jun 2021 15:08:00 +0300 Subject: [PATCH 434/476] UI: Add SNMP device profile configuration --- .../home/components/home-components.module.ts | 5 +- ...ofile-transport-configuration.component.ts | 4 +- ...ile-transport-configuration.component.html | 23 -- ...rofile-communication-config.component.html | 72 ++++++ ...rofile-communication-config.component.scss | 44 ++++ ...-profile-communication-config.component.ts | 218 ++++++++++++++++++ ...snmp-device-profile-mapping.component.html | 81 +++++++ ...snmp-device-profile-mapping.component.scss | 46 ++++ .../snmp-device-profile-mapping.component.ts | 165 +++++++++++++ ...ile-transport-configuration.component.html | 46 ++++ ...ofile-transport-configuration.component.ts | 57 +++-- .../snmp-device-profile-transport.module.ts | 38 +++ ui-ngx/src/app/shared/models/constants.ts | 16 ++ ui-ngx/src/app/shared/models/device.models.ts | 37 ++- .../assets/locale/locale.constant-en_US.json | 25 ++ 15 files changed, 834 insertions(+), 43 deletions(-) delete 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/snpm/snmp-device-profile-communication-config.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.html rename ui-ngx/src/app/modules/home/components/profile/device/{ => snpm}/snmp-device-profile-transport-configuration.component.ts (66%) create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport.module.ts 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 3958830088..d0ad42b62e 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 @@ -99,7 +99,6 @@ 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 { CoapDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/coap-device-profile-transport-configuration.component'; -import { SnmpDeviceProfileTransportConfigurationComponent } from '@home/components/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'; @@ -143,6 +142,7 @@ import { SecurityConfigLwm2mComponent } from '@home/components/device/security-c import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component'; import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component'; import { WidgetContainerComponent } from '@home/components/widget/widget-container.component'; +import { SnmpDeviceProfileTransportModule } from '@home/components/profile/device/snpm/snmp-device-profile-transport.module'; @NgModule({ declarations: @@ -228,7 +228,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain DefaultDeviceProfileTransportConfigurationComponent, MqttDeviceProfileTransportConfigurationComponent, CoapDeviceProfileTransportConfigurationComponent, - SnmpDeviceProfileTransportConfigurationComponent, DeviceProfileTransportConfigurationComponent, CreateAlarmRulesComponent, AlarmRuleComponent, @@ -272,6 +271,7 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain SharedModule, SharedHomeComponentsModule, Lwm2mProfileComponentsModule, + SnmpDeviceProfileTransportModule, StatesControllerModule ], exports: [ @@ -339,7 +339,6 @@ import { WidgetContainerComponent } from '@home/components/widget/widget-contain DefaultDeviceProfileTransportConfigurationComponent, MqttDeviceProfileTransportConfigurationComponent, CoapDeviceProfileTransportConfigurationComponent, - SnmpDeviceProfileTransportConfigurationComponent, DeviceProfileTransportConfigurationComponent, CreateAlarmRulesComponent, AlarmRuleComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts index 305a0a84b6..50b3bc801c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts @@ -89,7 +89,9 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu if (configuration) { delete configuration.type; } - this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + setTimeout(() => { + this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + }); } private updateModel() { 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 deleted file mode 100644 index f57b1ac716..0000000000 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.html +++ /dev/null @@ -1,23 +0,0 @@ - -
- - -
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.html new file mode 100644 index 0000000000..7d2f8a154c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.html @@ -0,0 +1,72 @@ + +
+
+
+ + device-profile.snmp.scope + + + {{ snmpSpecTypeTranslationMap.get(snmpSpecType) }} + + + + {{ 'device-profile.snmp.scope-required' | translate }} + + + +
+ + device-profile.snmp.querying-frequency + + + {{ 'device-profile.snmp.querying-frequency-required' | translate }} + + + {{ 'device-profile.snmp.querying-frequency-invalid-format' | translate }} + + + + +
+
+ +
+
+ device-profile.snmp.please-add-communication-config +
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.scss new file mode 100644 index 0000000000..3b6661b862 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.scss @@ -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. + */ +:host { + .communication-config { + border: 2px groove rgba(0, 0, 0, 0.25); + border-radius: 4px; + padding: 8px; + min-width: 0; + } + + .scope-row { + padding-bottom: 8px; + } + + .required-text { + margin: 16px 0 + } +} + +:host ::ng-deep { + .mat-form-field.spec { + .mat-form-field-infix { + width: 160px; + } + } + .button-icon{ + font-size: 20px; + width: 20px; + height: 20px; + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts new file mode 100644 index 0000000000..d683d46880 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts @@ -0,0 +1,218 @@ +/// +/// 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, OnDestroy, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + Validator, + Validators +} from '@angular/forms'; +import { SnmpCommunicationConfig, SnmpSpecType, SnmpSpecTypeTranslationMap } from '@shared/models/device.models'; +import { Subject, Subscription } from 'rxjs'; +import { isUndefinedOrNull } from '@core/utils'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + selector: 'tb-snmp-device-profile-communication-config', + templateUrl: './snmp-device-profile-communication-config.component.html', + styleUrls: ['./snmp-device-profile-communication-config.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SnmpDeviceProfileCommunicationConfigComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SnmpDeviceProfileCommunicationConfigComponent), + multi: true + }] +}) +export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator { + + snmpSpecTypes = Object.values(SnmpSpecType); + snmpSpecTypeTranslationMap = SnmpSpecTypeTranslationMap; + + deviceProfileCommunicationConfig: FormGroup; + + @Input() + disabled: boolean; + + private usedSpecType: SnmpSpecType[] = []; + private valueChange$: Subscription = null; + private destroy$ = new Subject(); + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder) { } + + ngOnInit(): void { + this.deviceProfileCommunicationConfig = this.fb.group({ + communicationConfig: this.fb.array([]) + }); + } + + ngOnDestroy() { + if (this.valueChange$) { + this.valueChange$.unsubscribe(); + } + this.destroy$.next(); + this.destroy$.complete(); + } + + communicationConfigFormArray(): FormArray { + return this.deviceProfileCommunicationConfig.get('communicationConfig') as FormArray; + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + if (this.disabled) { + this.deviceProfileCommunicationConfig.disable({emitEvent: false}); + } else { + this.deviceProfileCommunicationConfig.enable({emitEvent: false}); + } + } + + writeValue(communicationConfig: SnmpCommunicationConfig[]) { + if (this.valueChange$) { + this.valueChange$.unsubscribe(); + } + const communicationConfigControl: Array = []; + if (communicationConfig) { + communicationConfig.forEach((config) => { + communicationConfigControl.push(this.createdFormGroup(config)); + }); + } + this.deviceProfileCommunicationConfig.setControl('communicationConfig', this.fb.array(communicationConfigControl)); + if (!communicationConfig || !communicationConfig.length) { + this.addCommunicationConfig(); + } + if (this.disabled) { + this.deviceProfileCommunicationConfig.disable({emitEvent: false}); + } else { + this.deviceProfileCommunicationConfig.enable({emitEvent: false}); + } + this.valueChange$ = this.deviceProfileCommunicationConfig.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.updateUsedSpecType(); + if (!this.disabled && !this.deviceProfileCommunicationConfig.valid) { + this.updateModel(); + } + } + + public validate() { + return this.deviceProfileCommunicationConfig.valid && this.deviceProfileCommunicationConfig.value.communicationConfig.length ? null : { + communicationConfig: false + }; + } + + public removeCommunicationConfig(index: number) { + this.communicationConfigFormArray().removeAt(index); + } + + + get isAddEnabled(): boolean { + return this.communicationConfigFormArray().length !== Object.keys(SnmpSpecType).length; + } + + public addCommunicationConfig() { + this.communicationConfigFormArray().push(this.createdFormGroup()); + this.deviceProfileCommunicationConfig.updateValueAndValidity(); + if (!this.deviceProfileCommunicationConfig.valid) { + this.updateModel(); + } + } + + private getFirstUnusedSeverity(): SnmpSpecType { + for (const type of Object.values(SnmpSpecType)) { + if (this.usedSpecType.indexOf(type) === -1) { + return type; + } + } + return null; + } + + public isDisabledSeverity(type: SnmpSpecType, index: number): boolean { + const usedIndex = this.usedSpecType.indexOf(type); + return usedIndex > -1 && usedIndex !== index; + } + + public isShowFrequency(type: SnmpSpecType): boolean { + return type === SnmpSpecType.TELEMETRY_QUERYING || type === SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING; + } + + private updateUsedSpecType() { + this.usedSpecType = []; + const value: SnmpCommunicationConfig[] = this.deviceProfileCommunicationConfig.get('communicationConfig').value; + value.forEach((rule, index) => { + this.usedSpecType[index] = rule.spec; + }); + } + + private createdFormGroup(value?: SnmpCommunicationConfig): FormGroup { + if (isUndefinedOrNull(value)) { + value = { + spec: this.getFirstUnusedSeverity(), + queryingFrequencyMs: 0, + mappings: null + }; + } + const form = this.fb.group({ + spec: [value.spec, Validators.required], + mappings: [value.mappings] + }); + if (this.isShowFrequency(value.spec)) { + form.addControl('queryingFrequencyMs', + this.fb.control(value.queryingFrequencyMs, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')])); + } + form.get('spec').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(spec => { + if (this.isShowFrequency(spec)) { + form.addControl('queryingFrequencyMs', + this.fb.control(0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')])); + } else { + form.removeControl('queryingFrequencyMs'); + } + }); + return form; + } + + private updateModel() { + const value: SnmpCommunicationConfig[] = this.deviceProfileCommunicationConfig.get('communicationConfig').value; + value.forEach(config => { + if (!this.isShowFrequency(config.spec)) { + delete config.queryingFrequencyMs; + } + }); + this.updateUsedSpecType(); + this.propagateChange(value); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.html new file mode 100644 index 0000000000..a2a0a97d15 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.html @@ -0,0 +1,81 @@ + +
+
+
+ + + + +
+
+ +
+
+ + + + + {{ dataTypesTranslationMap.get(dataType) | translate }} + + + + {{ 'device-profile.snmp.data-type-required' | translate }} + + + + + + + {{ 'device-profile.snmp.data-key-required' | translate }} + + + + + + + {{ 'device-profile.snmp.oid-required' | translate }} + + + {{ 'device-profile.snmp.oid-pattern' | translate }} + + + +
+
+
+ device-profile.snmp.please-add-mapping-config +
+
+ +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss new file mode 100644 index 0000000000..94cf2290ac --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss @@ -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. + */ +:host { + .mapping-config { + min-width: 518px; + } + .mapping-list { + padding-bottom: 8px; + height: 46px; + } + + .required-text { + margin: 14px 0; + } +} + +:host ::ng-deep { + .mapping-list { + mat-form-field { + .mat-form-field-wrapper { + padding-bottom: 0; + .mat-form-field-infix { + border-top-width: 0.2em; + width: auto; + min-width: auto; + } + .mat-form-field-underline { + bottom: 0; + } + } + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.ts new file mode 100644 index 0000000000..53dbec422c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.ts @@ -0,0 +1,165 @@ +/// +/// 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, OnDestroy, OnInit } from '@angular/core'; +import { + AbstractControl, + ControlValueAccessor, + FormArray, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; +import { SnmpMapping } from '@shared/models/device.models'; +import { Subscription } from 'rxjs'; +import { DataType, DataTypeTranslationMap } from '@shared/models/constants'; +import { isUndefinedOrNull } from '@core/utils'; + +@Component({ + selector: 'tb-snmp-device-profile-mapping', + templateUrl: './snmp-device-profile-mapping.component.html', + styleUrls: ['./snmp-device-profile-mapping.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SnmpDeviceProfileMappingComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SnmpDeviceProfileMappingComponent), + multi: true + }] +}) +export class SnmpDeviceProfileMappingComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator { + + mappingsConfigForm: FormGroup; + + dataTypes = Object.values(DataType); + dataTypesTranslationMap = DataTypeTranslationMap; + + @Input() + disabled: boolean; + + private readonly oidPattern: RegExp = /^\.?([0-2])((\.0)|(\.[1-9][0-9]*))*$/; + + private valueChange$: Subscription = null; + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder) { } + + ngOnInit() { + this.mappingsConfigForm = this.fb.group({ + mappings: this.fb.array([]) + }); + } + + ngOnDestroy() { + if (this.valueChange$) { + this.valueChange$.unsubscribe(); + } + } + + registerOnChange(fn: any) { + this.propagateChange = fn; + } + + registerOnTouched(fn: any) { + } + + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + if (this.disabled) { + this.mappingsConfigForm.disable({emitEvent: false}); + } else { + this.mappingsConfigForm.enable({emitEvent: false}); + } + } + + validate(): ValidationErrors | null { + return this.mappingsConfigForm.valid && this.mappingsConfigForm.value.mappings.length ? null : { + mapping: false + }; + } + + writeValue(mappings: SnmpMapping[]) { + if (this.valueChange$) { + this.valueChange$.unsubscribe(); + } + const mappingsControl: Array = []; + if (mappings) { + mappings.forEach((config) => { + mappingsControl.push(this.createdFormGroup(config)); + }); + } + this.mappingsConfigForm.setControl('mappings', this.fb.array(mappingsControl)); + if (!mappings || !mappings.length) { + this.addMappingConfig(); + } + if (this.disabled) { + this.mappingsConfigForm.disable({emitEvent: false}); + } else { + this.mappingsConfigForm.enable({emitEvent: false}); + } + this.valueChange$ = this.mappingsConfigForm.valueChanges.subscribe(() => { + this.updateModel(); + }); + if (!this.disabled && !this.mappingsConfigForm.valid) { + this.updateModel(); + } + } + + mappingsConfigFormArray(): FormArray { + return this.mappingsConfigForm.get('mappings') as FormArray; + } + + public addMappingConfig() { + this.mappingsConfigFormArray().push(this.createdFormGroup()); + this.mappingsConfigForm.updateValueAndValidity(); + if (!this.mappingsConfigForm.valid) { + this.updateModel(); + } + } + + public removeMappingConfig(index: number) { + this.mappingsConfigFormArray().removeAt(index); + } + + private createdFormGroup(value?: SnmpMapping): FormGroup { + if (isUndefinedOrNull(value)) { + value = { + dataType: DataType.STRING, + key: '', + oid: '' + }; + } + return this.fb.group({ + dataType: [value.dataType, Validators.required], + key: [value.key, Validators.required], + oid: [value.oid, [Validators.required, Validators.pattern(this.oidPattern)]] + }); + } + + private updateModel() { + const value: SnmpMapping[] = this.mappingsConfigForm.get('mappings').value; + this.propagateChange(value); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.html new file mode 100644 index 0000000000..7dad337d72 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.html @@ -0,0 +1,46 @@ + +
+
+ + device-profile.snmp.timeout-ms + + + {{ 'device-profile.snmp.timeout-ms-required' | translate }} + + + {{ 'device-profile.snmp.timeout-ms-invalid-format' | translate }} + + + + device-profile.snmp.retries + + + {{ 'device-profile.snmp.retries-required' | translate }} + + + {{ 'device-profile.snmp.retries-invalid-format' | translate }} + + +
+
device-profile.snmp.communication-configs
+ + +
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/snpm/snmp-device-profile-transport-configuration.component.ts similarity index 66% rename from ui-ngx/src/app/modules/home/components/profile/device/snmp-device-profile-transport-configuration.component.ts rename to ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.ts index e6749a9219..a384acbae3 100644 --- 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/snpm/snmp-device-profile-transport-configuration.component.ts @@ -15,9 +15,16 @@ /// import { Component, forwardRef, Input, OnDestroy, 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 { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { DeviceProfileTransportConfiguration, @@ -40,19 +47,24 @@ export interface OidMappingConfiguration { 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 - }] + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SnmpDeviceProfileTransportConfigurationComponent), + multi: true + }] }) -export class SnmpDeviceProfileTransportConfigurationComponent implements ControlValueAccessor, OnInit, OnDestroy { +export class SnmpDeviceProfileTransportConfigurationComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator { snmpDeviceProfileTransportConfigurationFormGroup: FormGroup; private destroy$ = new Subject(); private requiredValue: boolean; - private configuration = []; get required(): boolean { return this.requiredValue; @@ -69,12 +81,14 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control private propagateChange = (v: any) => { } - constructor(private store: Store, private fb: FormBuilder) { + constructor(private fb: FormBuilder) { } ngOnInit(): void { this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({ - configuration: [null, Validators.required] + timeoutMs: [0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], + retries: [0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], + communicationConfigs: [null, Validators.required], }); this.snmpDeviceProfileTransportConfigurationFormGroup.valueChanges.pipe( takeUntil(this.destroy$) @@ -95,18 +109,33 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements Control registerOnTouched(fn: any): void { } + setDisabledState(isDisabled: boolean) { + this.disabled = isDisabled; + if (this.disabled) { + this.snmpDeviceProfileTransportConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.snmpDeviceProfileTransportConfigurationFormGroup.enable({emitEvent: false}); + } + } + writeValue(value: SnmpDeviceProfileTransportConfiguration | null): void { if (isDefinedAndNotNull(value)) { - this.snmpDeviceProfileTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + this.snmpDeviceProfileTransportConfigurationFormGroup.patchValue(value, {emitEvent: !value.communicationConfigs}); } } private updateModel() { let configuration: DeviceProfileTransportConfiguration = null; if (this.snmpDeviceProfileTransportConfigurationFormGroup.valid) { - configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue().configuration; + configuration = this.snmpDeviceProfileTransportConfigurationFormGroup.getRawValue(); configuration.type = DeviceTransportType.SNMP; } this.propagateChange(configuration); } + + validate(): ValidationErrors | null { + return this.snmpDeviceProfileTransportConfigurationFormGroup.valid ? null : { + snmpDeviceProfileTransportConfiguration: false + }; + } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport.module.ts b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport.module.ts new file mode 100644 index 0000000000..c0797d9b0e --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport.module.ts @@ -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. +/// + +import { NgModule } from '@angular/core'; +import { SharedModule } from '@shared/shared.module'; +import { CommonModule } from '@angular/common'; +import { SnmpDeviceProfileTransportConfigurationComponent } from '@home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component'; +import { SnmpDeviceProfileCommunicationConfigComponent } from './snmp-device-profile-communication-config.component'; +import { SnmpDeviceProfileMappingComponent } from './snmp-device-profile-mapping.component'; + +@NgModule({ + declarations: [ + SnmpDeviceProfileTransportConfigurationComponent, + SnmpDeviceProfileCommunicationConfigComponent, + SnmpDeviceProfileMappingComponent + ], + imports: [ + CommonModule, + SharedModule + ], + exports: [ + SnmpDeviceProfileTransportConfigurationComponent + ] +}) +export class SnmpDeviceProfileTransportModule { } diff --git a/ui-ngx/src/app/shared/models/constants.ts b/ui-ngx/src/app/shared/models/constants.ts index b72e2b196d..cd81753c76 100644 --- a/ui-ngx/src/app/shared/models/constants.ts +++ b/ui-ngx/src/app/shared/models/constants.ts @@ -147,6 +147,22 @@ export enum ValueType { JSON = 'JSON' } +export enum DataType { + STRING = 'STRING', + LONG = 'LONG', + BOOLEAN = 'BOOLEAN', + DOUBLE = 'DOUBLE', + JSON = 'JSON' +} + +export const DataTypeTranslationMap = new Map([ + [DataType.STRING, 'value.string'], + [DataType.LONG, 'value.integer'], + [DataType.BOOLEAN, 'value.boolean'], + [DataType.DOUBLE, 'value.double'], + [DataType.JSON, 'value.json'] +]); + export const valueTypesMap = new Map( [ [ diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 7af8e56472..568faeb452 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -29,6 +29,7 @@ import * as _moment from 'moment'; import { AbstractControl, ValidationErrors } from '@angular/forms'; import { OtaPackageId } from '@shared/models/id/ota-package-id'; import { DashboardId } from '@shared/models/id/dashboard-id'; +import { DataType } from '@shared/models/constants'; export enum DeviceProfileType { DEFAULT = 'DEFAULT', @@ -257,7 +258,35 @@ export interface Lwm2mDeviceProfileTransportConfiguration { } export interface SnmpDeviceProfileTransportConfiguration { - [key: string]: any; + timeoutMs?: number; + retries?: number; + communicationConfigs?: SnmpCommunicationConfig[]; +} + +export enum SnmpSpecType { + TELEMETRY_QUERYING = 'TELEMETRY_QUERYING', + CLIENT_ATTRIBUTES_QUERYING = 'CLIENT_ATTRIBUTES_QUERYING', + SHARED_ATTRIBUTES_SETTING = 'SHARED_ATTRIBUTES_SETTING', + TO_DEVICE_RPC_REQUEST = 'TO_DEVICE_RPC_REQUEST' +} + +export const SnmpSpecTypeTranslationMap = new Map([ + [SnmpSpecType.TELEMETRY_QUERYING, ' Telemetry'], + [SnmpSpecType.CLIENT_ATTRIBUTES_QUERYING, 'Client attributes'], + [SnmpSpecType.SHARED_ATTRIBUTES_SETTING, 'Shared attributes'], + [SnmpSpecType.TO_DEVICE_RPC_REQUEST, 'RPC request'] +]); + +export interface SnmpCommunicationConfig { + spec: SnmpSpecType; + mappings: SnmpMapping[]; + queryingFrequencyMs?: number; +} + +export interface SnmpMapping { + oid: string; + key: string; + dataType: DataType; } export type DeviceProfileTransportConfigurations = DefaultDeviceProfileTransportConfiguration & @@ -332,7 +361,11 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M}; break; case DeviceTransportType.SNMP: - const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = {}; + const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = { + timeoutMs: 0, + retries: 0, + communicationConfigs: null + }; transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP}; break; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index ec084b8a3a..95dc0c2d72 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1297,6 +1297,31 @@ "sw-update-recourse": "Software update CoAP recourse", "sw-update-recourse-required": "Software update CoAP recourse is required.", "config-json-tab": "Json Config Profile Device" + }, + "snmp": { + "add-communication-config": "Add communication config", + "add-mapping": "Add mapping", + "communication-configs": "Communication configs", + "data-key": "Data key", + "data-key-required": "Data key is required.", + "data-type": "Data type", + "data-type-required": "Data type is required.", + "oid": "OID", + "oid-pattern": "Invalid OID format.", + "oid-required": "OID is required.", + "please-add-communication-config": "Please add communication config", + "please-add-mapping-config": "Please add mapping config", + "querying-frequency": "Querying frequency, ms", + "querying-frequency-invalid-format": "Querying frequency must be a positive integer.", + "querying-frequency-required": "Querying frequency is required.", + "retries": "Retries", + "retries-invalid-format": "Retries must be a positive integer.", + "retries-required": "Retries is required.", + "scope": "Scope", + "scope-required": "Scope is required.", + "timeout-ms": "Timeout, ms", + "timeout-ms-invalid-format": "Timeout must be a positive integer.", + "timeout-ms-required": "Timeout is required." } }, "dialog": { From e4dd517edd1a0d471a4ea0aa3e687ef26184713e Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 11 Jun 2021 12:36:03 +0300 Subject: [PATCH 435/476] cache: added cache executor service --- .../src/main/resources/thingsboard.yml | 1 + .../dao/cache/CacheExecutorService.java | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 54721ba2fa..ffdb783b12 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -332,6 +332,7 @@ actors: cache: # caffeine or redis type: "${CACHE_TYPE:caffeine}" + maximumPoolSize: "${CACHE_MAXIMUM_POOL_SIZE:16}" # max pool size to process futures that calls the external cache 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}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java new file mode 100644 index 0000000000..ca00e41d79 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java @@ -0,0 +1,48 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.dao.cache; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; + +@Component +public class CacheExecutorService extends AbstractListeningExecutor { + + @Value("${cache.maximumPoolSize}") + private int poolSize; + + @Override + protected int getThreadPollSize() { + return poolSize; + } + +} From c843b2f09e67a084e11a34d8c7f7f81075adc4b8 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 11 Jun 2021 12:36:51 +0300 Subject: [PATCH 436/476] cache: CachedAttributesService replaced directExecutor with CacheExecutorService --- .../dao/attributes/CachedAttributesService.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index 87ddfeee9a..92c75760e2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -17,11 +17,9 @@ package org.thingsboard.server.dao.attributes; 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.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; @@ -32,6 +30,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.stats.DefaultCounter; import org.thingsboard.server.common.stats.StatsFactory; +import org.thingsboard.server.dao.cache.CacheExecutorService; import org.thingsboard.server.dao.service.Validator; import java.util.ArrayList; @@ -45,7 +44,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import static org.thingsboard.server.common.data.CacheConstants.ATTRIBUTES_CACHE; import static org.thingsboard.server.dao.attributes.AttributeUtils.validate; @Service @@ -59,12 +57,15 @@ public class CachedAttributesService implements AttributesService { private final AttributesCacheWrapper cacheWrapper; private final DefaultCounter hitCounter; private final DefaultCounter missCounter; + private final CacheExecutorService cacheExecutorService; public CachedAttributesService(AttributesDao attributesDao, AttributesCacheWrapper cacheWrapper, - StatsFactory statsFactory) { + StatsFactory statsFactory, + CacheExecutorService cacheExecutorService) { this.attributesDao = attributesDao; this.cacheWrapper = cacheWrapper; + this.cacheExecutorService = cacheExecutorService; this.hitCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "hit"); this.missCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "miss"); @@ -88,7 +89,7 @@ public class CachedAttributesService implements AttributesService { // TODO: think if it's a good idea to store 'empty' attributes cacheWrapper.put(attributeCacheKey, foundAttrKvEntry.orElse(null)); return foundAttrKvEntry; - }, MoreExecutors.directExecutor()); + }, cacheExecutorService); } } @@ -111,7 +112,7 @@ public class CachedAttributesService implements AttributesService { notFoundAttributeKeys.removeAll(wrappedCachedAttributes.keySet()); ListenableFuture> result = attributesDao.find(tenantId, entityId, scope, notFoundAttributeKeys); - return Futures.transform(result, foundInDbAttributes -> mergeDbAndCacheAttributes(entityId, scope, cachedAttributes, notFoundAttributeKeys, foundInDbAttributes), MoreExecutors.directExecutor()); + return Futures.transform(result, foundInDbAttributes -> mergeDbAndCacheAttributes(entityId, scope, cachedAttributes, notFoundAttributeKeys, foundInDbAttributes), cacheExecutorService); } @@ -169,7 +170,7 @@ public class CachedAttributesService implements AttributesService { // TODO: can do if (attributesCache.get() != null) attributesCache.put() instead, but will be more twice more requests to cache List attributeKeys = attributes.stream().map(KvEntry::getKey).collect(Collectors.toList()); - future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), MoreExecutors.directExecutor()); + future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), cacheExecutorService); return future; } @@ -177,7 +178,7 @@ public class CachedAttributesService implements AttributesService { public ListenableFuture> removeAll(TenantId tenantId, EntityId entityId, String scope, List attributeKeys) { validate(entityId, scope); ListenableFuture> future = attributesDao.removeAll(tenantId, entityId, scope, attributeKeys); - future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), MoreExecutors.directExecutor()); + future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), cacheExecutorService); return future; } From be61b3c841328b7d0d4d5570512fefcd9d852b30 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 14 Jun 2021 14:39:00 +0300 Subject: [PATCH 437/476] cache: CachedAttributesService Executor choice depends on local or remote cache type. For local - directExecutor, for remote (IO calls) - dedicated threadPool to unblock any caller thread. Test added. --- .../attributes/CachedAttributesService.java | 1 + .../CachedAttributesServiceTest.java | 88 +++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java diff --git a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java index 92c75760e2..d4e29f02de 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/attributes/CachedAttributesService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.attributes; 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.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.Cache; diff --git a/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java new file mode 100644 index 0000000000..af980a6301 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java @@ -0,0 +1,88 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.dao.attributes; + +import com.google.common.util.concurrent.MoreExecutors; +import org.junit.Test; +import org.thingsboard.server.dao.cache.CacheConfiguration; +import org.thingsboard.server.dao.cache.CacheExecutorService; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.willCallRealMethod; +import static org.mockito.Mockito.mock; + +public class CachedAttributesServiceTest { + + public static final String REDIS = "redis"; + + @Test + public void givenLocalCacheTypeName_whenEquals_thenOK() { + assertThat(CachedAttributesService.LOCAL_CACHE_TYPE, is("caffeine")); + } + + @Test + public void givenCacheType_whenGetExecutor_thenDirectExecutor() { + CachedAttributesService cachedAttributesService = mock(CachedAttributesService.class); + CacheExecutorService cacheExecutorService = mock(CacheExecutorService.class); + willCallRealMethod().given(cachedAttributesService).getExecutor(any(), any()); + + assertThat(cachedAttributesService.getExecutor(null, cacheExecutorService), is(MoreExecutors.directExecutor())); + + CacheConfiguration cacheConfiguration = new CacheConfiguration(); + cacheConfiguration.setType(null); + assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); + + cacheConfiguration.setType(""); + assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); + + cacheConfiguration.setType(CachedAttributesService.LOCAL_CACHE_TYPE); + assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); + + } + + @Test + public void givenCacheType_whenGetExecutor_thenReturnCacheExecutorService() { + CachedAttributesService cachedAttributesService = mock(CachedAttributesService.class); + CacheExecutorService cacheExecutorService = mock(CacheExecutorService.class); + willCallRealMethod().given(cachedAttributesService).getExecutor(any(CacheConfiguration.class), any(CacheExecutorService.class)); + + CacheConfiguration cacheConfiguration = new CacheConfiguration(); + cacheConfiguration.setType(REDIS); + assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(cacheExecutorService)); + + cacheConfiguration.setType("unknownCacheType"); + assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(cacheExecutorService)); + + } + +} \ No newline at end of file From 0c1e608051ac4e77cade38a27587635351e54737 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 17 Jun 2021 16:35:30 +0300 Subject: [PATCH 438/476] UI: Refactoring lwm2m object list --- .../device/lwm2m/lwm2m-object-list.component.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) 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 ae7c3ccdc6..62d96b69e0 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 @@ -89,6 +89,13 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V objectsList: [this.objectsList], objectLwm2m: [''] }); + this.lwm2mListFormGroup.valueChanges.subscribe((value) => { + let formValue = null; + if (this.lwm2mListFormGroup.valid) { + formValue = value; + } + this.propagateChange(formValue); + }); } private updateValidators = (): void => { @@ -142,7 +149,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V this.objectsList = []; this.modelValue = []; } - this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList, {emitEvents: false}); + this.lwm2mListFormGroup.patchValue({objectsList: this.objectsList}, {emitEvent: false}); this.dirty = false; } } @@ -195,9 +202,9 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } } - private clear = (): void => { + private clear() { this.searchText = ''; - this.lwm2mListFormGroup.get('objectLwm2m').patchValue(null); + this.lwm2mListFormGroup.get('objectLwm2m').patchValue(null, {emitEvent: false}); setTimeout(() => { this.objectInput.nativeElement.blur(); this.objectInput.nativeElement.focus(); From 248e35cb249eff03b25e8b983701181191e0ada1 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 17 Jun 2021 17:05:55 +0300 Subject: [PATCH 439/476] UI: Fixed not updated view entities table, after updated values --- .../home/components/widget/lib/alarms-table-widget.component.ts | 1 + .../components/widget/lib/entities-table-widget.component.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts index 138c56d307..390559bab3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts @@ -274,6 +274,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, this.updateTitle(true); this.alarmsDatasource.updateAlarms(); this.clearCache(); + this.ctx.detectChanges(); } public pageLinkSortDirection(): SortDirection { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts index 8994051223..68db8a0dcc 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts @@ -47,7 +47,6 @@ import { isDefined, isNumber, isObject, - isString, isUndefined } from '@core/utils'; import cssjs from '@core/css/css'; @@ -236,6 +235,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni this.updateTitle(true); this.entityDatasource.dataUpdated(); this.clearCache(); + this.ctx.detectChanges(); } public pageLinkSortDirection(): SortDirection { From bed454ec0fcace61736ca58129d1b74646fdffba Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 17 Jun 2021 18:29:33 +0300 Subject: [PATCH 440/476] LwM2M Refactoring of OTA --- .../src/main/resources/thingsboard.yml | 7 +- .../transport/TransportSqlTestSuite.java | 14 +- .../config/LwM2MTransportServerConfig.java | 20 +- .../server/DefaultLwM2mTransportService.java | 5 +- .../lwm2m/server/LwM2mNetworkConfig.java | 6 +- .../server/LwM2mTransportCoapResource.java | 9 +- .../lwm2m/server/LwM2mTransportUtil.java | 46 +- .../lwm2m/server/UpdateResultFw.java | 13 +- .../DefaultLwM2MAttributesService.java | 14 +- .../lwm2m/server/client/LwM2mClient.java | 22 - .../lwm2m/server/client/LwM2mFwSwUpdate.java | 486 ------------------ .../common/LwM2MExecutorAwareService.java | 41 ++ .../AbstractTbLwM2MRequestCallback.java | 12 +- .../DefaultLwM2mDownlinkMsgHandler.java | 62 ++- .../TbLwM2MCancelAllObserveCallback.java | 8 +- .../TbLwM2MCancelObserveCallback.java | 7 +- .../downlink/TbLwM2MDeleteCallback.java | 5 +- .../downlink/TbLwM2MDiscoverCallback.java | 5 +- .../downlink/TbLwM2MExecuteCallback.java | 5 +- .../downlink/TbLwM2MObserveCallback.java | 7 +- .../server/downlink/TbLwM2MReadCallback.java | 7 +- .../downlink/TbLwM2MTargetedCallback.java | 7 +- .../TbLwM2MUplinkTargetedCallback.java | 35 ++ .../TbLwM2MWriteAttributesCallback.java | 5 +- .../TbLwM2MWriteResponseCallback.java | 7 +- .../log/DefaultLwM2MTelemetryLogService.java | 56 ++ .../server/log/LwM2MTelemetryLogService.java | 26 + .../ota/DefaultLwM2MOtaUpdateService.java | 163 ++++-- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 23 +- .../server/ota/LwM2MOtaUpdateService.java | 6 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 22 +- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 197 ++----- .../server/uplink/LwM2mUplinkMsgHandler.java | 4 - .../src/main/resources/tb-lwm2m-transport.yml | 8 +- 34 files changed, 479 insertions(+), 881 deletions(-) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MUplinkTargetedCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/LwM2MTelemetryLogService.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 54721ba2fa..fcde169fc9 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -677,12 +677,11 @@ transport: 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}" + uplink_pool_size: "${LWM2M_UPLINK_POOL_SIZE:10}" + downlink_pool_size: "${LWM2M_DOWNLINK_POOL_SIZE:10}" + ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}" registration_store_pool_size: "${LWM2M_REGISTRATION_STORE_POOL_SIZE:100}" clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}" - 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}" diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java index d059ea1449..25df3bee00 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportSqlTestSuite.java @@ -26,13 +26,13 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ -// "org.thingsboard.server.transport.*.rpc.sql.*Test", -// "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", -// "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", -// "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", -// "org.thingsboard.server.transport.*.attributes.request.sql.*Test", -// "org.thingsboard.server.transport.*.claim.sql.*Test", -// "org.thingsboard.server.transport.*.provision.sql.*Test", + "org.thingsboard.server.transport.*.rpc.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.timeseries.sql.*Test", + "org.thingsboard.server.transport.*.telemetry.attributes.sql.*Test", + "org.thingsboard.server.transport.*.attributes.updates.sql.*Test", + "org.thingsboard.server.transport.*.attributes.request.sql.*Test", + "org.thingsboard.server.transport.*.claim.sql.*Test", + "org.thingsboard.server.transport.*.provision.sql.*Test", "org.thingsboard.server.transport.lwm2m.*Test" }) public class TransportSqlTestSuite { 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 25c7766895..a2ff361712 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 @@ -57,29 +57,21 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { private boolean recommendedSupportedGroups; @Getter - @Value("${transport.lwm2m.response_pool_size:}") - private int responsePoolSize; + @Value("${transport.lwm2m.downlink_pool_size:}") + private int downlinkPoolSize; @Getter - @Value("${transport.lwm2m.registered_pool_size:}") - private int registeredPoolSize; + @Value("${transport.lwm2m.uplink_pool_size:}") + private int uplinkPoolSize; @Getter - @Value("${transport.lwm2m.registration_store_pool_size:}") - private int registrationStorePoolSize; + @Value("${transport.lwm2m.ota_pool_size:}") + private int otaPoolSize; @Getter @Value("${transport.lwm2m.clean_period_in_sec:}") private int cleanPeriodInSec; - @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; 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 97ab82f802..9425ef7891 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 @@ -27,6 +27,7 @@ import org.eclipse.leshan.server.californium.LeshanServerBuilder; import org.eclipse.leshan.server.californium.registration.CaliforniumRegistrationStore; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.springframework.stereotype.Component; +import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; @@ -80,6 +81,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; private final LwM2mTransportServerHelper helper; + private final OtaPackageDataCache otaPackageDataCache; private final DefaultLwM2MUplinkMsgHandler handler; private final CaliforniumRegistrationStore registrationStore; private final TbSecurityStore securityStore; @@ -103,8 +105,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { * "coap://host:port/{path}/{token}/{nameFile}" */ - - LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(handler, FIRMWARE_UPDATE_COAP_RECOURSE); + LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(otaPackageDataCache, FIRMWARE_UPDATE_COAP_RECOURSE); this.server.coap().getServer().add(otaCoapResource); this.startLhServer(); this.context.setServer(server); 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 00f70dbcf7..4fc60aecfc 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 @@ -84,10 +84,10 @@ public class LwM2mNetworkConfig { Create new instance of udp endpoint context matcher. Params: checkAddress - – true with address check, (STRICT, UDP) + – true with address check, (STRICT, UDP) - if port Registration of client is changed - it is bad - false, without */ - coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "STRICT"); + coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED"); /** https://tools.ietf.org/html/rfc7959#section-2.9.3 The block size (number of bytes) to use when doing a blockwise transfer. \ @@ -103,7 +103,7 @@ public class LwM2mNetworkConfig { */ coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); - coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); + coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10); return coapConfig; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java index ff86ee9bd9..229ec1a08c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java @@ -24,6 +24,7 @@ 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.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; @@ -39,11 +40,11 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.S public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { private final ConcurrentMap tokenToObserveRelationMap = new ConcurrentHashMap<>(); private final ConcurrentMap tokenToObserveNotificationSeqMap = new ConcurrentHashMap<>(); - private final LwM2mUplinkMsgHandler handler; + private final OtaPackageDataCache otaPackageDataCache; - public LwM2mTransportCoapResource(LwM2mUplinkMsgHandler handler, String name) { + public LwM2mTransportCoapResource(OtaPackageDataCache otaPackageDataCache, String name) { super(name); - this.handler = handler; + this.otaPackageDataCache = otaPackageDataCache; this.setObservable(true); // enable observing this.addObserver(new CoapResourceObserver()); } @@ -154,7 +155,7 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { } private byte[] getOtaData(UUID currentId) { - return ((DefaultLwM2MUplinkMsgHandler) handler).otaPackageDataCache.get(currentId.toString()); + return otaPackageDataCache.get(currentId.toString()); } } 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 f310eb6481..d04e4993f1 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 @@ -201,36 +201,12 @@ public class LwM2mTransportUtil { } } - /** - * FirmwareUpdateStatus { - * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED - */ - public static OtaPackageUpdateStatus equalsFwSateFwResultToFirmwareUpdateStatus(UpdateStateFw updateStateFw, UpdateResultFw updateResultFw) { - switch (updateResultFw) { - case INITIAL: - return toOtaPackageUpdateStatus(updateStateFw); - case UPDATE_SUCCESSFULLY: - return UPDATED; - case NOT_ENOUGH: - case OUT_OFF_MEMORY: - case CONNECTION_LOST: - case INTEGRITY_CHECK_FAILURE: - case UNSUPPORTED_TYPE: - case INVALID_URI: - case UPDATE_FAILED: - case UNSUPPORTED_PROTOCOL: - return FAILED; - default: - throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", updateStateFw.name(), updateResultFw.name()); - } - } - - public static OtaPackageUpdateStatus toOtaPackageUpdateStatus(UpdateResultFw updateResultFw) { + public static Optional toOtaPackageUpdateStatus(UpdateResultFw updateResultFw) { switch (updateResultFw) { case INITIAL: - return VERIFIED; + return Optional.empty(); case UPDATE_SUCCESSFULLY: - return UPDATED; + return Optional.of(UPDATED); case NOT_ENOUGH: case OUT_OFF_MEMORY: case CONNECTION_LOST: @@ -239,22 +215,22 @@ public class LwM2mTransportUtil { case INVALID_URI: case UPDATE_FAILED: case UNSUPPORTED_PROTOCOL: - return FAILED; + return Optional.of(FAILED); default: throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", updateResultFw.name()); } } - public static OtaPackageUpdateStatus toOtaPackageUpdateStatus(UpdateStateFw updateStateFw) { + public static Optional toOtaPackageUpdateStatus(UpdateStateFw updateStateFw) { switch (updateStateFw) { case IDLE: - return VERIFIED; + return Optional.empty(); case DOWNLOADING: - return DOWNLOADING; + return Optional.of(DOWNLOADING); case DOWNLOADED: - return DOWNLOADED; + return Optional.of(DOWNLOADED); case UPDATING: - return UPDATING; + return Optional.of(UPDATING); default: throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", updateStateFw); } @@ -471,7 +447,7 @@ public class LwM2mTransportUtil { return lwM2mOtaConvert; } else if (FW_RESULT_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); - lwM2mOtaConvert.setValue(UpdateResultFw.fromUpdateResultFwByCode(((Long) value).intValue()).type); + lwM2mOtaConvert.setValue(UpdateResultFw.fromUpdateResultFwByCode(((Long) value).intValue()).getType()); return lwM2mOtaConvert; } else if (SW_UPDATE_STATE_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); @@ -585,7 +561,7 @@ public class LwM2mTransportUtil { public static String fromVersionedIdToObjectId(String pathIdVer) { try { - if(pathIdVer == null) { + if (pathIdVer == null) { return null; } String[] keyArray = pathIdVer.split(LWM2M_SEPARATOR_PATH); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java index 1b2e7d6b26..79361aeb1d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.server; +import lombok.Getter; + /** * FW Update Result * 0: Initial value. Once the updating process is initiated (Download /Update), this Resource MUST be reset to Initial value. @@ -40,14 +42,17 @@ public enum UpdateResultFw { UPDATE_FAILED(8, "Firmware update failed", false), UNSUPPORTED_PROTOCOL(9, "Unsupported protocol", false); - public int code; - public String type; - public boolean isAgain; + @Getter + private int code; + @Getter + private String type; + @Getter + private boolean again; UpdateResultFw(int code, String type, boolean isAgain) { this.code = code; this.type = type; - this.isAgain = isAgain; + this.again = isAgain; } public static UpdateResultFw fromUpdateResultFwByType(String type) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java index ed717f3eee..ee723dbaa6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/attributes/DefaultLwM2MAttributesService.java @@ -39,6 +39,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; @@ -71,6 +72,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { private final LwM2MTransportServerConfig config; private final LwM2mUplinkMsgHandler uplinkHandler; private final LwM2mDownlinkMsgHandler downlinkHandler; + private final LwM2MTelemetryLogService logService; private final LwM2MOtaUpdateService otaUpdateService; @Override @@ -150,14 +152,6 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { if (!otherAttributes.isEmpty()) { onAttributesUpdate(lwM2MClient, otherAttributes); } - } else if (msg.getSharedDeletedCount() > 0 && lwM2MClient != null) { - msg.getSharedUpdatedList().forEach(tsKvProto -> { - String pathName = tsKvProto.getKv().getKey(); - Object valueNew = getValueFromKvProto(tsKvProto.getKv()); - if (OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) && !valueNew.equals(lwM2MClient.getFwUpdate().getCurrentVersion())) { - lwM2MClient.getFwUpdate().setCurrentVersion((String) valueNew); - } - }); } else if (lwM2MClient == null) { log.error("OnAttributeUpdate, lwM2MClient is null"); } @@ -197,12 +191,12 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { private void pushUpdateToClientIfNeeded(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(this.config.getTimeout()).build(); - downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, lwM2MClient, versionedId)); + downlinkHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, lwM2MClient, versionedId)); } else { log.error("Failed update resource [{}] [{}]", versionedId, newValue); String logMsg = String.format("%s: Failed update resource versionedId - %s value - %s. Value is not changed or bad", LOG_LWM2M_ERROR, versionedId, newValue); - uplinkHandler.logToTelemetry(lwM2MClient, logMsg); + logService.log(lwM2MClient, logMsg); log.info("Failed update resource [{}] [{}]", versionedId, newValue); } } 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 21d6ec3d48..0e71a1e865 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 @@ -95,12 +95,6 @@ public class LwM2mClient implements Cloneable { private UUID profileId; @Getter @Setter - private volatile LwM2mFwSwUpdate fwUpdate; - @Getter - @Setter - private volatile LwM2mFwSwUpdate swUpdate; - @Getter - @Setter private Registration registration; private ValidateDeviceCredentialsResponse credentials; @@ -365,21 +359,5 @@ public class LwM2mClient implements Cloneable { } } - public LwM2mFwSwUpdate getFwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClientContext clientContext) { - if (this.fwUpdate == null) { - var profile = clientContext.getProfile(this.getProfileId()); - this.fwUpdate = new LwM2mFwSwUpdate(handler, this, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy()); - } - return this.fwUpdate; - } - - public LwM2mFwSwUpdate getSwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClientContext clientContext) { - if (this.swUpdate == null) { - var profile = clientContext.getProfile(this.getProfileId()); - this.swUpdate = new LwM2mFwSwUpdate(handler, this, OtaPackageType.SOFTWARE, profile.getClientLwM2mSettings().getSwUpdateStrategy()); - } - return this.fwUpdate; - } - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java deleted file mode 100644 index b39f6084cd..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mFwSwUpdate.java +++ /dev/null @@ -1,486 +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.Getter; -import lombok.Setter; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; -import org.eclipse.leshan.server.registration.Registration; -import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; -import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; -import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; - -import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; -import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; -import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; -import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_3_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_19_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_5_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_URI_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.EXECUTE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.WRITE_REPLACE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_NAME_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_RESULT_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UN_INSTALL_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_UPDATE_STATE_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.toOtaPackageUpdateStatus; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.splitCamelCaseString; - -@Slf4j -public class LwM2mFwSwUpdate { - // 5/0/6 PkgName - // 9/0/0 PkgName - @Getter - @Setter - private volatile String currentTitle; - // 5/0/7 PkgVersion - // 9/0/1 PkgVersion - @Getter - @Setter - private volatile String currentVersion; - @Getter - @Setter - private volatile UUID currentId; - @Getter - @Setter - private volatile String stateUpdate; - @Getter - private String pathPackageId; - @Getter - private String pathStateId; - @Getter - private String pathResultId; - @Getter - private String pathNameId; - @Getter - private String pathVerId; - @Getter - private String pathInstallId; - @Getter - private String pathUnInstallId; - @Getter - private String wUpdate; - @Getter - @Setter - private volatile boolean infoFwSwUpdate = false; - private final OtaPackageType type; - - private final LwM2mUplinkMsgHandler handler; - - @Getter - LwM2mClient lwM2MClient; - @Getter - private final List pendingInfoRequestsStart; - @Getter - @Setter - private volatile int updateStrategy; - - public LwM2mFwSwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClient lwM2MClient, OtaPackageType type, int updateStrategy) { - this.handler = handler; - this.lwM2MClient = lwM2MClient; - this.pendingInfoRequestsStart = new CopyOnWriteArrayList<>(); - this.type = type; - this.stateUpdate = null; - this.updateStrategy = updateStrategy; - this.initPathId(); - } - - private void initPathId() { - if (FIRMWARE.equals(this.type)) { - this.pathPackageId = LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy ? - FW_PACKAGE_5_ID : LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy ? - FW_PACKAGE_URI_ID : FW_PACKAGE_19_ID; - this.pathStateId = FW_STATE_ID; - this.pathResultId = FW_RESULT_ID; - this.pathNameId = FW_NAME_ID; - this.pathVerId = FW_5_VER_ID; - this.pathInstallId = FW_UPDATE_ID; - this.wUpdate = FW_UPDATE; - } else if (SOFTWARE.equals(this.type)) { - this.pathPackageId = SW_PACKAGE_ID; - this.pathStateId = SW_UPDATE_STATE_ID; - this.pathResultId = SW_RESULT_ID; - this.pathNameId = SW_NAME_ID; - this.pathVerId = SW_VER_ID; - this.pathInstallId = SW_INSTALL_ID; - this.pathUnInstallId = SW_UN_INSTALL_ID; - this.wUpdate = SW_UPDATE; - } - } - - public void initReadValue(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request, String pathIdVer) { - if (pathIdVer != null) { - this.pendingInfoRequestsStart.remove(pathIdVer); - } - if (this.pendingInfoRequestsStart.size() == 0) { - this.infoFwSwUpdate = false; -// if (!FAILED.name().equals(this.stateUpdate)) { - boolean conditionalStart = this.type.equals(FIRMWARE) ? this.conditionalFwUpdateStart(handler) : - this.conditionalSwUpdateStart(handler); - if (conditionalStart) { - this.writeFwSwWare(handler, request); - } -// } - } - } - - /** - * Send FsSw to Lwm2mClient: - * before operation Write: fw_state = DOWNLOADING - */ - public void writeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) { - if (this.currentId != null) { - this.stateUpdate = OtaPackageUpdateStatus.INITIATED.name(); - this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LWM2M_INFO, null); - String targetIdVer = convertObjectIdToVersionedId(this.pathPackageId, this.lwM2MClient.getRegistration()); - String fwMsg = String.format("%s: Start type operation %s paths: %s", LOG_LWM2M_INFO, - LwM2mOperationType.FW_UPDATE.name(), this.pathPackageId); - handler.logToTelemetry(fwMsg, lwM2MClient.getRegistration().getId()); - log.warn("8) Start firmware Update. Send save to: [{}] ver: [{}] path: [{}]", this.lwM2MClient.getDeviceName(), this.currentVersion, targetIdVer); - if (LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy) { - int chunkSize = 0; - int chunk = 0; - byte[] firmwareChunk = handler.otaPackageDataCache.get(this.currentId.toString(), chunkSize, chunk); - - TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(firmwareChunk).timeout(handler.config.getTimeout()).build(); - request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteResponseCallback(handler, lwM2MClient, targetIdVer)); - } else if (LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) { - String apiFont = "coap://176.36.143.9:5685"; - String uri = apiFont + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + this.currentId.toString(); - log.warn("89) coapUri: [{}]", uri); - //TODO: user this.rpcRequest??? - TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(uri).timeout(handler.config.getTimeout()).build(); - request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteResponseCallback(handler, lwM2MClient, targetIdVer)); - } else if (LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) { - - } - } else { - String msgError = "FirmWareId is null."; - log.warn("6) [{}]", msgError); -// if (this.rpcRequest != null) { -// TODO: refactor. -// handler.sentRpcResponse(this.rpcRequest, CONTENT.name(), msgError, LOG_LW2M_ERROR); -// } - log.error(msgError); - this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LWM2M_ERROR, msgError); - } - } - - public void sendLogs(DefaultLwM2MUplinkMsgHandler handler, String typeOper, String typeInfo, String msgError) { -// this.sendSateOnThingsBoard(handler); - String msg = String.format("%s: %s, %s, pkgVer: %s: pkgName - %s state - %s.", - typeInfo, this.wUpdate, typeOper, this.currentVersion, this.currentTitle, this.stateUpdate); - if (LOG_LWM2M_ERROR.equals(typeInfo)) { - msg = String.format("%s Error: %s", msg, msgError); - } - handler.logToTelemetry(lwM2MClient, msg); - } - - - /** - * After inspection Update Result - * fw_state/sw_state = UPDATING - * send execute - */ - public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) { - this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_INFO, null); - //TODO: user this.rpcRequest??? - TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(pathInstallId).timeout(handler.config.getTimeout()).build(); - request.sendExecuteRequest(lwM2MClient, downlink, new TbLwM2MExecuteCallback(handler, lwM2MClient, pathInstallId)); - } - - /** - * Firmware start: Check if the version has changed and launch a new update. - * -ObjectId 5, Binary or ObjectId 5, URI - * -- If the result of the update - errors (more than 1) - This means that the previous. the update failed. - * - We launch the update regardless of the state of the firmware and its version. - * -- If the result of the update - errors (more than 1) - This means that the previous. the update failed. - * * ObjectId 5, Binary - * -- If the result of the update is not errors (equal to 1 or 0) and ver in Object 5 is not empty - it means that the previous update has passed. - * Compare current versions by equals. - * * ObjectId 5, URI - * -- If the result of the update is not errors (equal to 1 or 0) and ver in Object 5 is not empty - it means that the previous update has passed. - * Compare current versions by contains. - */ - private boolean conditionalFwUpdateStart(DefaultLwM2MUplinkMsgHandler handler) { - Long updateResultFw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - String ver5 = (String) this.lwM2MClient.getResourceValue(null, this.pathVerId); - String pathName = (String) this.lwM2MClient.getResourceValue(null, this.pathNameId); - String ver3 = (String) this.lwM2MClient.getResourceValue(null, FW_3_VER_ID); - // #1/#2 - String fwMsg = null; - if ((this.currentVersion != null && ( - ver5 != null && ver5.equals(this.currentVersion) || - ver3 != null && ver3.contains(this.currentVersion) - )) || - (this.currentTitle != null && pathName != null && this.currentTitle.equals(pathName))) { - fwMsg = String.format("%s: The update was interrupted. The device has the same version: %s.", LOG_LWM2M_ERROR, - this.currentVersion); - } else if (updateResultFw != null && updateResultFw > UpdateResultFw.UPDATE_SUCCESSFULLY.code) { - fwMsg = String.format("%s: The update was interrupted. The device has the status UpdateResult: error (%d).", LOG_LWM2M_ERROR, - updateResultFw); - } - if (fwMsg != null) { - handler.logToTelemetry(fwMsg, lwM2MClient.getRegistration().getId()); - return false; - } else { - return true; - } - } - - - /** - * Before operation Execute inspection Update Result : - * 0 - Initial value - */ - public boolean conditionalFwExecuteStart() { - Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return UpdateResultFw.INITIAL.code == updateResult; - } - - /** - * After operation Execute success inspection Update Result : - * 1 - "Firmware updated successfully" - */ - public boolean conditionalFwExecuteAfterSuccess() { - Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return UpdateResultFw.UPDATE_SUCCESSFULLY.code == updateResult; - } - - /** - * After operation Execute success inspection Update Result : - * > 1 error: "Firmware updated successfully" - */ - public boolean conditionalFwExecuteAfterError() { - Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return UpdateResultFw.UPDATE_SUCCESSFULLY.code < updateResult; - } - - /** - * Software start - * - If Update Result -errors (equal or more than 50) - This means that the previous. the update failed. - * * - We launch the update regardless of the state of the firmware and its version. - * - If Update Result is not errors (less than 50) and ver is not empty - This means that before. the update has passed. - * - If Update Result is not errors and ver is empty - This means that there was no update yet or before. UnInstall update - * - If Update Result is not errors and ver is not empty - This means that before unInstall update - * * - Check if the version has changed and launch a new update. - */ - private boolean conditionalSwUpdateStart(DefaultLwM2MUplinkMsgHandler handler) { - Long updateResultSw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - // #1/#2 - return updateResultSw >= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code || - ( - (updateResultSw <= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code - ) && - ( - (this.currentVersion != null && !this.currentVersion.equals(this.lwM2MClient.getResourceValue(null, this.pathVerId))) || - (this.currentTitle != null && !this.currentTitle.equals(this.lwM2MClient.getResourceValue(null, this.pathNameId))) - ) - ); - } - - /** - * Before operation Execute inspection Update Result : - * 3 - Successfully Downloaded and package integrity verified - */ - public boolean conditionalSwUpdateExecute() { - Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_DOWNLOADED_VERIFIED.code == updateResult; - } - - /** - * After finish operation Execute (success): - * -- inspection Update Result: - * ---- FW если Update Result == 1 ("Firmware updated successfully") или SW если Update Result == 2 ("Software successfully installed.") - * -- fw_state/sw_state = UPDATED - *

- * After finish operation Execute (error): - * -- inspection updateResult and send to thingsboard info about error - * --- send to telemetry ( key - this is name Update Result in model) ( - * -- fw_state/sw_state = FAILED - */ - public void finishFwSwUpdate(DefaultLwM2MUplinkMsgHandler handler, boolean success) { - Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - String value = FIRMWARE.equals(this.type) ? UpdateResultFw.fromUpdateResultFwByCode(updateResult.intValue()).type : - LwM2mTransportUtil.UpdateResultSw.fromUpdateResultSwByCode(updateResult.intValue()).type; - String key = splitCamelCaseString((String) this.lwM2MClient.getResourceNameByRezId(null, this.pathResultId)); - if (success) { - this.stateUpdate = OtaPackageUpdateStatus.UPDATED.name(); - this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_INFO, null); - } else { - this.stateUpdate = OtaPackageUpdateStatus.FAILED.name(); - this.sendLogs(handler, EXECUTE.name(), LOG_LWM2M_ERROR, value); - } - handler.helper.sendParametersOnThingsboardTelemetry( - handler.helper.getKvStringtoThingsboard(key, value), this.lwM2MClient.getSession()); - } - - /** - * After operation Execute success inspection Update Result : - * 2 - "Software successfully installed." - */ - public boolean conditionalSwExecuteAfterSuccess() { - Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return LwM2mTransportUtil.UpdateResultSw.SUCCESSFULLY_INSTALLED.code == updateResult; - } - - /** - * After operation Execute success inspection Update Result : - * >= 50 - error "NOT_ENOUGH_STORAGE" - */ - public boolean conditionalSwExecuteAfterError() { - Long updateResult = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); - return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult; - } - -// private void observeStateUpdate(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) { -// request.sendAllRequest(lwM2MClient, -// convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE, -// null, null, 0, null); -// request.sendAllRequest(lwM2MClient, -// convertPathFromObjectIdToIdVer(this.pathResultId, this.lwM2MClient.getRegistration()), OBSERVE, -// null, null, 0, null); -// } - - public void sendSateOnThingsBoard(DefaultLwM2MUplinkMsgHandler handler) { - if (StringUtils.trimToNull(this.stateUpdate) != null) { - List result = new ArrayList<>(); - TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(this.type, STATE)); - kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(stateUpdate); - result.add(kvProto.build()); - handler.helper.sendParametersOnThingsboardTelemetry(result, - handler.getSessionInfoOrCloseSession(this.lwM2MClient.getRegistration())); - } - } - - public void sendReadObserveInfo(LwM2mDownlinkMsgHandler request) { - this.infoFwSwUpdate = true; - this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( - this.pathStateId, this.lwM2MClient.getRegistration())); - this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( - this.pathResultId, this.lwM2MClient.getRegistration())); - this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( - FW_3_VER_ID, this.lwM2MClient.getRegistration())); - if (LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code == this.updateStrategy || - LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy || - SOFTWARE.equals(this.type)) { - this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( - this.pathVerId, this.lwM2MClient.getRegistration())); - this.pendingInfoRequestsStart.add(convertObjectIdToVersionedId( - this.pathNameId, this.lwM2MClient.getRegistration())); - } - this.pendingInfoRequestsStart.forEach(versionedId -> { - TbLwM2MObserveRequest downlink = TbLwM2MObserveRequest.builder().versionedId(versionedId).build(); - request.sendObserveRequest(this.lwM2MClient, downlink, new TbLwM2MObserveCallback(handler, lwM2MClient, versionedId)); - }); - - } - - /** - * Before operation Execute (FwUpdate) inspection Update Result : - * - after finished operation Write result: success (FwUpdate): fw_state = DOWNLOADED - * - before start operation Execute (FwUpdate) Update Result = 0 - Initial value - * - start Execute (FwUpdate) - * After finished operation Execute (FwUpdate) inspection Update Result : - * - after start operation Execute (FwUpdate): fw_state = UPDATING - * - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully") - * - finished operation Execute (FwUpdate) - */ - public void updateStateOta(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request, - Registration registration, String path, int value) { - if (OBJ_5_BINARY.code == this.getUpdateStrategy()) { - if ((convertObjectIdToVersionedId(FW_RESULT_ID, registration).equals(path))) { - if (DOWNLOADED.name().equals(this.getStateUpdate()) - && this.conditionalFwExecuteStart()) { - this.executeFwSwWare(handler, request); - } else if (UPDATING.name().equals(this.getStateUpdate()) - && this.conditionalFwExecuteAfterSuccess()) { - this.finishFwSwUpdate(handler, true); - } else if (UPDATING.name().equals(this.getStateUpdate()) - && this.conditionalFwExecuteAfterError()) { - this.finishFwSwUpdate(handler, false); - } - } - } else if (OBJ_5_TEMP_URL.code == this.getUpdateStrategy()) { - if (this.currentId != null && (convertObjectIdToVersionedId(FW_STATE_ID, registration).equals(path))) { - String state = toOtaPackageUpdateStatus(UpdateStateFw.fromStateFwByCode(value)).name(); - if (StringUtils.isNotEmpty(state) && !FAILED.name().equals(this.stateUpdate) && !state.equals(this.stateUpdate)) { - this.stateUpdate = state; - this.sendSateOnThingsBoard(handler); - } - if (value == UpdateStateFw.DOWNLOADED.code) { - this.executeFwSwWare(handler, request); - } - handler.firmwareUpdateState.put(lwM2MClient.getEndpoint(), value); - } - if ((convertObjectIdToVersionedId(FW_RESULT_ID, registration).equals(path))) { - if (this.currentId != null && value == UpdateResultFw.INITIAL.code) { - this.setStateUpdate(INITIATED.name()); - } else if (this.currentId != null && value == UpdateResultFw.UPDATE_SUCCESSFULLY.code) { - this.setStateUpdate(UPDATED.name()); - } else if (value > UpdateResultFw.UPDATE_SUCCESSFULLY.code) { - this.setStateUpdate(FAILED.name()); - } - this.sendSateOnThingsBoard(handler); - } - } else if (OBJ_19_BINARY.code == this.getUpdateStrategy()) { - - } - } -} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java new file mode 100644 index 0000000000..1f1dff784f --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/common/LwM2MExecutorAwareService.java @@ -0,0 +1,41 @@ +/** + * 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.common; + +import org.thingsboard.common.util.ThingsBoardExecutors; + +import javax.annotation.PreDestroy; +import java.util.concurrent.ExecutorService; + +public abstract class LwM2MExecutorAwareService { + + protected ExecutorService executor; + + protected abstract int getExecutorSize(); + + protected abstract String getExecutorName(); + + protected void init() { + this.executor = ThingsBoardExecutors.newWorkStealingPool(getExecutorSize(), getExecutorName()); + } + + public void destroy() { + if (executor != null) { + executor.shutdownNow(); + } + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java index 764d5ff5ef..561b103277 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java @@ -17,30 +17,30 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; @Slf4j public abstract class AbstractTbLwM2MRequestCallback implements DownlinkRequestCallback { - protected final LwM2mUplinkMsgHandler handler; + protected final LwM2MTelemetryLogService logService; protected final LwM2mClient client; - protected AbstractTbLwM2MRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) { - this.handler = handler; + protected AbstractTbLwM2MRequestCallback(LwM2MTelemetryLogService logService, LwM2mClient client) { + this.logService = logService; this.client = client; } @Override public void onValidationError(String params, String msg) { log.trace("[{}] Request [{}] validation failed. Reason: {}", client.getEndpoint(), params, msg); - handler.logToTelemetry(client, String.format("[%s]: Request [%s] validation failed. Reason: %s", LOG_LWM2M_WARN, params, msg)); + logService.log(client, String.format("[%s]: Request [%s] validation failed. Reason: %s", LOG_LWM2M_WARN, params, msg)); } @Override public void onError(String params, Exception e) { log.trace("[{}] Request [{}] processing failed", client.getEndpoint(), params, e); - handler.logToTelemetry(client, String.format("[%s]: Request [%s] processing failed. Reason: %s", LOG_LWM2M_WARN, params, e)); + logService.log(client, String.format("[%s]: Request [%s] processing failed. Reason: %s", LOG_LWM2M_WARN, params, e)); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index da513a1934..52ef83bf8e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java @@ -26,7 +26,6 @@ import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.ObjectLink; import org.eclipse.leshan.core.node.codec.CodecException; 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; @@ -45,7 +44,6 @@ import org.eclipse.leshan.core.response.ReadResponse; 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.registration.Registration; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; @@ -54,17 +52,18 @@ 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.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -74,25 +73,38 @@ import static org.eclipse.leshan.core.attributes.Attribute.LESSER_THAN; 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.STEP; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_REQUEST_CHANNEL; @Slf4j @Service @TbLwM2mTransportComponent @RequiredArgsConstructor -public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { - private ExecutorService responseRequestExecutor; +public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService implements LwM2mDownlinkMsgHandler { public LwM2mValueConverterImpl converter; private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; + private final LwM2MTelemetryLogService logService; @PostConstruct public void init() { + super.init(); this.converter = LwM2mValueConverterImpl.getInstance(); - responseRequestExecutor = Executors.newFixedThreadPool(this.config.getResponsePoolSize(), - new NamedThreadFactory(String.format("LwM2M %s channel response after request", RESPONSE_REQUEST_CHANNEL))); + } + + @PreDestroy + public void destroy() { + super.destroy(); + } + + @Override + protected int getExecutorSize() { + return config.getDownlinkPoolSize(); + } + + @Override + protected String getExecutorName() { + return "LwM2M Downlink"; } @Override @@ -221,7 +233,8 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { **/ Collection resources = client.getNewResourceForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); - WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, convertResourceModelTypeToContentFormat(client, resourceModelWrite.type), resultIds.getObjectId(), + ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); + WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); sendRequest(client, downlink, request.getTimeout(), callback); } else if (resultIds.isObjectInstance()) { @@ -245,19 +258,24 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { private , T extends LwM2mResponse> void sendRequest(LwM2mClient client, R request, long timeoutInMs, DownlinkRequestCallback callback) { Registration registration = client.getRegistration(); - context.getServer().send(registration, request, timeoutInMs, response -> { - responseRequestExecutor.submit(() -> { - try { - callback.onSuccess(request, response); - } catch (Exception e) { - log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, e); - } + try { + logService.log(client, String.format("[%s][%s] Sending request: %s to %s", registration.getId(), registration.getSocketAddress(), request.getClass().getSimpleName(), request.getPath())); + context.getServer().send(registration, request, timeoutInMs, response -> { + executor.submit(() -> { + try { + callback.onSuccess(request, response); + } catch (Exception e) { + log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, e); + } + }); + }, e -> { + executor.submit(() -> { + callback.onError(JacksonUtil.toString(request), e); + }); }); - }, e -> { - responseRequestExecutor.submit(() -> { - callback.onError(JacksonUtil.toString(request), e); - }); - }); + } catch (Exception e) { + callback.onError(JacksonUtil.toString(request), e); + } } private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java index 42f2a1baec..f90e0f8b97 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java @@ -17,21 +17,21 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; @Slf4j public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback { - public TbLwM2MCancelAllObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) { - super(handler, client); + public TbLwM2MCancelAllObserveCallback(LwM2MTelemetryLogService logService, LwM2mClient client) { + super(logService, client); } @Override public void onSuccess(TbLwM2MCancelAllRequest request, Integer canceledSubscriptionsCount) { log.trace("[{}] Cancel of all observations was successful: {}", client.getEndpoint(), canceledSubscriptionsCount); - handler.logToTelemetry(client, String.format("[%s]: Cancel of all observations was successful. Result: [%s]", LOG_LWM2M_INFO, canceledSubscriptionsCount)); + logService.log(client, String.format("[%s]: Cancel of all observations was successful. Result: [%s]", LOG_LWM2M_INFO, canceledSubscriptionsCount)); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java index eec33963d5..311be03462 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java @@ -16,6 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @@ -27,15 +28,15 @@ public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback private final String versionedId; - public TbLwM2MCancelObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String versionedId) { - super(handler, client); + public TbLwM2MCancelObserveCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) { + super(logService, client); this.versionedId = versionedId; } @Override public void onSuccess(TbLwM2MCancelObserveRequest request, Integer canceledSubscriptionsCount) { log.trace("[{}] Cancel observation of [{}] successful: {}", client.getEndpoint(), versionedId, canceledSubscriptionsCount); - handler.logToTelemetry(client, String.format("[%s]: Cancel Observe for [%s] successful. Result: [%s]", LOG_LWM2M_INFO, versionedId, canceledSubscriptionsCount)); + logService.log(client, String.format("[%s]: Cancel Observe for [%s] successful. Result: [%s]", LOG_LWM2M_INFO, versionedId, canceledSubscriptionsCount)); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java index 32c82404c6..4c0a758572 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java @@ -18,12 +18,13 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.DeleteRequest; import org.eclipse.leshan.core.response.DeleteResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; public class TbLwM2MDeleteCallback extends TbLwM2MTargetedCallback { - public TbLwM2MDeleteCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client, targetId); + public TbLwM2MDeleteCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(logService, client, targetId); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java index 5ae20e064d..41172f0599 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java @@ -17,13 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.DiscoverRequest; import org.eclipse.leshan.core.response.DiscoverResponse; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; public class TbLwM2MDiscoverCallback extends TbLwM2MTargetedCallback { - public TbLwM2MDiscoverCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client, targetId); + public TbLwM2MDiscoverCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(logService, client, targetId); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java index d2aed6a4fd..992cf6d616 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java @@ -17,13 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.ExecuteRequest; import org.eclipse.leshan.core.response.ExecuteResponse; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; public class TbLwM2MExecuteCallback extends TbLwM2MTargetedCallback { - public TbLwM2MExecuteCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client, targetId); + public TbLwM2MExecuteCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(logService, client, targetId); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java index 7fca29aa8c..dde49c870d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java @@ -18,14 +18,15 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.response.ObserveResponse; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @Slf4j -public class TbLwM2MObserveCallback extends TbLwM2MTargetedCallback { +public class TbLwM2MObserveCallback extends TbLwM2MUplinkTargetedCallback { - public TbLwM2MObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client, targetId); + public TbLwM2MObserveCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(handler, logService, client, targetId); } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java index 657fd04fd0..59247b103b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java @@ -19,13 +19,14 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; @Slf4j -public class TbLwM2MReadCallback extends TbLwM2MTargetedCallback { +public class TbLwM2MReadCallback extends TbLwM2MUplinkTargetedCallback { - public TbLwM2MReadCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client, targetId); + public TbLwM2MReadCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(handler, logService, client, targetId); } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java index 258bec53ac..373c882ec4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MTargetedCallback.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; @@ -26,8 +27,8 @@ public abstract class TbLwM2MTargetedCallback extends AbstractTbLwM2MReque protected final String versionedId; - public TbLwM2MTargetedCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String versionedId) { - super(handler, client); + public TbLwM2MTargetedCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) { + super(logService, client); this.versionedId = versionedId; } @@ -36,7 +37,7 @@ public abstract class TbLwM2MTargetedCallback extends AbstractTbLwM2MReque //TODO convert camelCase to "camel case" using .split("(? extends TbLwM2MTargetedCallback { + + protected LwM2mUplinkMsgHandler handler; + + public TbLwM2MUplinkTargetedCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String versionedId) { + super(logService, client, versionedId); + this.handler = handler; + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java index 372534fa7a..e346ad965a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java @@ -18,12 +18,13 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; public class TbLwM2MWriteAttributesCallback extends TbLwM2MTargetedCallback { - public TbLwM2MWriteAttributesCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client, targetId); + public TbLwM2MWriteAttributesCallback(LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(logService, client, targetId); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java index 89586bd113..4746077f19 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteResponseCallback.java @@ -18,12 +18,13 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; -public class TbLwM2MWriteResponseCallback extends TbLwM2MTargetedCallback { +public class TbLwM2MWriteResponseCallback extends TbLwM2MUplinkTargetedCallback { - public TbLwM2MWriteResponseCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { - super(handler, client, targetId); + public TbLwM2MWriteResponseCallback(LwM2mUplinkMsgHandler handler, LwM2MTelemetryLogService logService, LwM2mClient client, String targetId) { + super(handler, logService, client, targetId); } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.java new file mode 100644 index 0000000000..421f4cf3a6 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/DefaultLwM2MTelemetryLogService.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.transport.lwm2m.server.log; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; + +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; + +@Slf4j +@Service +@TbLwM2mTransportComponent +@RequiredArgsConstructor +public class DefaultLwM2MTelemetryLogService implements LwM2MTelemetryLogService { + + private final LwM2mClientContext clientContext; + private final LwM2mTransportServerHelper helper; + + /** + * @param logMsg - text msg + * @param registrationId - Id of Registration LwM2M Client + */ + @Override + public void log(String registrationId, String logMsg) { + log(clientContext.getClientByRegistrationId(registrationId), logMsg); + } + + @Override + public void log(LwM2mClient client, String logMsg) { + if (logMsg != null && client != null && client.getSession() != null) { + if (logMsg.length() > 1024) { + logMsg = logMsg.substring(0, 1024); + } + this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), client.getSession()); + } + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/LwM2MTelemetryLogService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/LwM2MTelemetryLogService.java new file mode 100644 index 0000000000..ff8543303e --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/log/LwM2MTelemetryLogService.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.transport.lwm2m.server.log; + +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public interface LwM2MTelemetryLogService { + + void log(LwM2mClient client, String msg); + + void log(String registrationId, String msg); + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 3f86131320..d44770e591 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.DonAsynchron; -import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageKey; @@ -41,33 +40,35 @@ import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; 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.common.LwM2MExecutorAwareService; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteUpdateRequest; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; 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 static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; @Slf4j @Service @TbLwM2mTransportComponent @RequiredArgsConstructor -public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { +public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService implements LwM2MOtaUpdateService { public static final String FIRMWARE_VERSION = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION); public static final String FIRMWARE_TITLE = getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE); @@ -93,8 +94,8 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { private final LwM2mUplinkMsgHandler uplinkHandler; private final LwM2mDownlinkMsgHandler downlinkHandler; private final OtaPackageDataCache otaPackageDataCache; + private final LwM2MTelemetryLogService logService; private final LwM2mTransportServerHelper helper; - private ExecutorService executor; @Autowired @Lazy @@ -102,8 +103,22 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { @PostConstruct public void init() { - //TODO: define parallelism in constant - executor = ThingsBoardExecutors.newWorkStealingPool(4, "LwM2M OTA Updates"); + super.init(); + } + + @PreDestroy + public void destroy() { + super.destroy(); + } + + @Override + protected int getExecutorSize() { + return config.getOtaPoolSize(); + } + + @Override + protected String getExecutorName() { + return "LwM2M OTA"; } @Override @@ -112,7 +127,6 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { //TODO: check that the client supports FW and SW by checking the supported objects in the model. List attributesToFetch = new ArrayList<>(); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); - fwInfo.setSupported(client.isValidObjectVersion(FW_NAME_ID) || client.isValidObjectVersion(FW_VER_ID)); if (fwInfo.isSupported()) { attributesToFetch.add(FIRMWARE_TITLE); attributesToFetch.add(FIRMWARE_VERSION); @@ -138,6 +152,14 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { } } + @Override + public void forceFirmwareUpdate(LwM2mClient client) { + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setRetryAttempts(0); + fwInfo.setFailedPackageId(null); + startFirmwareUpdateIfNeeded(client, fwInfo); + } + @Override public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional newFirmwareUrl) { LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); @@ -147,33 +169,48 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { @Override public void onCurrentFirmwareNameUpdate(LwM2mClient client, String name) { + log.debug("[{}] Current fw name: {}", client.getEndpoint(), name); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); fwInfo.setCurrentName(name); } @Override - public void onCurrentFirmwareVersionUpdate(LwM2mClient client, String version) { + public void onCurrentFirmwareVersion3Update(LwM2mClient client, String version) { + log.debug("[{}] Current fw version: {}", client.getEndpoint(), version); + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setCurrentVersion3(version); + } + + @Override + public void onCurrentFirmwareVersion5Update(LwM2mClient client, String version) { + log.debug("[{}] Current fw version: {}", client.getEndpoint(), version); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); - fwInfo.setCurrentVersion(version); + fwInfo.setCurrentVersion5(version); } @Override - public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long state) { + public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long stateCode) { + log.debug("[{}] Current fw state: {}", client.getEndpoint(), stateCode); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); - UpdateStateFw newState = UpdateStateFw.fromStateFwByCode(state.intValue()); - if (UpdateStateFw.DOWNLOADED.equals(newState)) { + UpdateStateFw state = UpdateStateFw.fromStateFwByCode(stateCode.intValue()); + if (UpdateStateFw.DOWNLOADED.equals(state)) { executeFwUpdate(client); } - fwInfo.setUpdateState(newState); - sendStateUpdateToTelemetry(client, fwInfo, LwM2mTransportUtil.toOtaPackageUpdateStatus(newState)); + fwInfo.setUpdateState(state); + Optional status = LwM2mTransportUtil.toOtaPackageUpdateStatus(state); + status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, + otaStatus, "Firmware Update State: " + state.name())); } @Override public void onCurrentFirmwareResultUpdate(LwM2mClient client, Long code) { + log.debug("[{}] Current fw result: {}", client.getEndpoint(), code); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); UpdateResultFw result = UpdateResultFw.fromUpdateResultFwByCode(code.intValue()); - sendStateUpdateToTelemetry(client, fwInfo, LwM2mTransportUtil.toOtaPackageUpdateStatus(result)); - if (result.isAgain && fwInfo.getRetryAttempts() <= 2) { + Optional status = LwM2mTransportUtil.toOtaPackageUpdateStatus(result); + status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, + otaStatus, "Firmware Update Result: " + result.name())); + if (result.isAgain() && fwInfo.getRetryAttempts() <= 2) { fwInfo.setRetryAttempts(fwInfo.getRetryAttempts() + 1); startFirmwareUpdateIfNeeded(client, fwInfo); } else { @@ -183,6 +220,7 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { @Override public void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient client, Long value) { + log.debug("[{}] Current fw delivery method: {}", client.getEndpoint(), value); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); fwInfo.setDeliveryMethod(value.intValue()); } @@ -193,22 +231,29 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { } private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo fwInfo) { - if (fwInfo.isUpdateRequired()) { - if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) { - log.info("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl()); - startFirmwareUpdateUsingUrl(client, fwInfo.getTargetUrl()); - } else { - startFirmwareUpdateUsingBinary(client, fwInfo); + try { + if (!fwInfo.isSupported()) { + log.debug("[{}] Fw update is not supported: {}", client.getEndpoint(), fwInfo); + sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Client does not support firmware update or profile misconfiguration!"); + } else if (fwInfo.isUpdateRequired()) { + if (StringUtils.isNotEmpty(fwInfo.getTargetUrl())) { + log.debug("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion(), fwInfo.getTargetUrl()); + startFirmwareUpdateUsingUrl(client, fwInfo.getTargetUrl()); + } else { + log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion()); + startFirmwareUpdateUsingBinary(client, fwInfo); + } } - } else if (fwInfo.isUpdateFailed()) { - sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED); + } catch (Exception e) { + log.warn("[{}] failed to update client: {}", client.getEndpoint(), fwInfo, e); + sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Internal server error: " + e.getMessage()); } } private void startFirmwareUpdateUsingUrl(LwM2mClient client, String url) { String targetIdVer = convertObjectIdToVersionedId(FW_URL_ID, client.getRegistration()); - TbLwM2MWriteUpdateRequest request = TbLwM2MWriteUpdateRequest.builder().versionedId(targetIdVer).value(url).timeout(config.getTimeout()).build(); - downlinkHandler.sendWriteUpdateRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, client, targetIdVer)); + TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(url).timeout(config.getTimeout()).build(); + downlinkHandler.sendWriteReplaceRequest(client, request, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, targetIdVer)); } public void startFirmwareUpdateUsingBinary(LwM2mClient client, LwM2MClientOtaInfo fwInfo) { @@ -217,41 +262,44 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { new TransportServiceCallback<>() { @Override public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { - if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) - && response.getType().equals(OtaPackageType.FIRMWARE.name())) { - UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); - LwM2MFirmwareUpdateStrategy strategy; - if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == 2) { - strategy = fwInfo.getStrategy(); - } else { - strategy = fwInfo.getDeliveryMethod() == 0 ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; - } - switch (strategy) { - case OBJ_5_BINARY: - byte[] firmwareChunk = otaPackageDataCache.get(otaPackageId.toString(), 0, 0); - TbLwM2MWriteReplaceRequest writeRequest = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) - .value(firmwareChunk).contentFormat(ContentFormat.OPAQUE) - .timeout(config.getTimeout()).build(); - downlinkHandler.sendWriteReplaceRequest(client, writeRequest, new TbLwM2MWriteResponseCallback(uplinkHandler, client, versionedId)); - break; - case OBJ_5_TEMP_URL: - startFirmwareUpdateUsingUrl(client, fwInfo.getBaseUrl() + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + otaPackageId.toString()); - break; - default: - //TODO: send log to telemetry - } - } else { - //TODO: send log to telemetry - } + executor.submit(() -> doUpdateFirmwareUsingBinary(response, fwInfo, versionedId, client)); } @Override public void onError(Throwable e) { - log.trace("Failed to process firmwareUpdate ", e); + logService.log(client, "Failed to process firmware update: " + e.getMessage()); } }); } + private void doUpdateFirmwareUsingBinary(TransportProtos.GetOtaPackageResponseMsg response, LwM2MClientOtaInfo fwInfo, String versionedId, LwM2mClient client) { + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { + UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); + LwM2MFirmwareUpdateStrategy strategy; + if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == 2) { + strategy = fwInfo.getStrategy(); + } else { + strategy = fwInfo.getDeliveryMethod() == 0 ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; + } + switch (strategy) { + case OBJ_5_BINARY: + byte[] firmwareChunk = otaPackageDataCache.get(otaPackageId.toString(), 0, 0); + TbLwM2MWriteReplaceRequest writeRequest = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) + .value(firmwareChunk).contentFormat(ContentFormat.OPAQUE) + .timeout(config.getTimeout()).build(); + downlinkHandler.sendWriteReplaceRequest(client, writeRequest, new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId)); + break; + case OBJ_5_TEMP_URL: + startFirmwareUpdateUsingUrl(client, fwInfo.getBaseUrl() + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + otaPackageId.toString()); + break; + default: + sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Unsupported strategy: " + strategy.name()); + } + } else { + sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Failed to fetch OTA package: " + response.getResponseStatus()); + } + } + private TransportProtos.GetOtaPackageRequestMsg createOtaPackageRequestMsg(TransportProtos.SessionInfoProto sessionInfo, String nameFwSW) { return TransportProtos.GetOtaPackageRequestMsg.newBuilder() .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) @@ -264,7 +312,7 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { private void executeFwUpdate(LwM2mClient client) { TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(FW_EXECUTE_ID).timeout(config.getTimeout()).build(); - downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(uplinkHandler, client, FW_EXECUTE_ID)); + downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, FW_EXECUTE_ID)); } private Optional getAttributeValue(List attrs, String keyName) { @@ -298,11 +346,14 @@ public class DefaultLwM2MOtaUpdateService implements LwM2MOtaUpdateService { } - private void sendStateUpdateToTelemetry(LwM2mClient client, LwM2MClientOtaInfo fwInfo, OtaPackageUpdateStatus status) { + private void sendStateUpdateToTelemetry(LwM2mClient client, LwM2MClientOtaInfo fwInfo, OtaPackageUpdateStatus status, String log) { List result = new ArrayList<>(); TransportProtos.KeyValueProto.Builder kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(getAttributeKey(fwInfo.getType(), STATE)); kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(status.name()); result.add(kvProto.build()); + kvProto = TransportProtos.KeyValueProto.newBuilder().setKey(LOG_LWM2M_TELEMETRY); + kvProto.setType(TransportProtos.KeyValueType.STRING_V).setStringV(log); + result.add(kvProto.build()); helper.sendParametersOnThingsboardTelemetry(result, client.getSession()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index 50b44f8d06..43d4f4acf5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -31,7 +31,6 @@ public class LwM2MClientOtaInfo { private final OtaPackageType type; private String baseUrl; - private boolean supported; private boolean targetFetchFailure; private String targetName; @@ -40,7 +39,8 @@ public class LwM2MClientOtaInfo { private boolean currentFetchFailure; private String currentName; - private String currentVersion; + private String currentVersion3; + private String currentVersion5; private Integer deliveryMethod; //TODO: use value from device if applicable; @@ -65,24 +65,27 @@ public class LwM2MClientOtaInfo { } public boolean isUpdateRequired() { - if (StringUtils.isEmpty(targetName) || - StringUtils.isEmpty(targetVersion) || - (StringUtils.isEmpty(currentName) && StringUtils.isEmpty(currentVersion))) { + if (StringUtils.isEmpty(targetName) || StringUtils.isEmpty(targetVersion) || !isSupported()) { return false; } else { String targetPackageId = getPackageId(targetName, targetVersion); - String currentPackageId = getPackageId(currentName, currentVersion); + String currentPackageIdUsingObject5 = getPackageId(currentName, currentVersion5); if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) { return false; } else { - return !targetPackageId.equals(currentPackageId); + if (targetPackageId.equals(currentPackageIdUsingObject5)) { + return false; + } else if (StringUtils.isNotEmpty(currentVersion3)) { + return !currentVersion3.contains(targetPackageId); + } else { + return true; + } } } } - public boolean isUpdateFailed() { - return (StringUtils.isNotEmpty(targetName) && StringUtils.isNotEmpty(targetVersion) && - (StringUtils.isEmpty(currentName) && StringUtils.isEmpty(currentVersion))); + public boolean isSupported() { + return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion5) || StringUtils.isNotEmpty(currentVersion3); } public void setUpdateResult(UpdateResultFw updateResult) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java index 6386e94364..9c7905a5e8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java @@ -23,13 +23,17 @@ public interface LwM2MOtaUpdateService { void init(LwM2mClient client); + void forceFirmwareUpdate(LwM2mClient client); + void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional newFirmwareUrl); void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion); void onCurrentFirmwareNameUpdate(LwM2mClient client, String name); - void onCurrentFirmwareVersionUpdate(LwM2mClient client, String version); + void onCurrentFirmwareVersion3Update(LwM2mClient client, String version); + + void onCurrentFirmwareVersion5Update(LwM2mClient client, String version); void onCurrentFirmwareStateUpdate(LwM2mClient client, Long state); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 533458ce76..5de223fa2f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -50,6 +50,7 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttrib import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteUpdateRequest; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Map; @@ -69,6 +70,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private final LwM2MTransportServerConfig config; private final LwM2mUplinkMsgHandler uplinkHandler; private final LwM2mDownlinkMsgHandler downlinkHandler; + private final LwM2MTelemetryLogService logService; private final Map rpcSubscriptions = new ConcurrentHashMap<>(); @Override @@ -148,14 +150,14 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendReadRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MReadCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MReadCallback(uplinkHandler, logService, client, versionedId); var rpcCallback = new RpcReadResponseCallback<>(transportService, client, requestMsg, versionedId, mainCallback); downlinkHandler.sendReadRequest(client, request, rpcCallback); } private void sendObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { TbLwM2MObserveRequest request = TbLwM2MObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MObserveCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MObserveCallback(uplinkHandler, logService, client, versionedId); var rpcCallback = new RpcReadResponseCallback<>(transportService, client, requestMsg, versionedId, mainCallback); downlinkHandler.sendObserveRequest(client, request, rpcCallback); } @@ -172,14 +174,14 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { private void sendDiscoverRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MDiscoverCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MDiscoverCallback(logService, client, versionedId); var rpcCallback = new RpcDiscoverCallback(transportService, client, requestMsg, mainCallback); downlinkHandler.sendDiscoverRequest(client, request, rpcCallback); } private void sendExecuteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MExecuteCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MExecuteCallback(logService, client, versionedId); var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); downlinkHandler.sendExecuteRequest(client, downlink, rpcCallback); } @@ -189,7 +191,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(versionedId) .attributes(requestBody.getAttributes()) .timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MWriteAttributesCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MWriteAttributesCallback(logService, client, versionedId); var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); downlinkHandler.sendWriteAttributesRequest(client, request, rpcCallback); } @@ -198,7 +200,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { RpcWriteUpdateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteUpdateRequest.class); TbLwM2MWriteUpdateRequest.TbLwM2MWriteUpdateRequestBuilder builder = TbLwM2MWriteUpdateRequest.builder().versionedId(versionedId); builder.value(requestBody.getValue()).timeout(this.config.getTimeout()); - var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId); var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); downlinkHandler.sendWriteUpdateRequest(client, builder.build(), rpcCallback); } @@ -208,28 +210,28 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) .value(requestBody.getValue()) .timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId); var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); downlinkHandler.sendWriteReplaceRequest(client, request, rpcCallback); } private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { TbLwM2MCancelObserveRequest downlink = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MCancelObserveCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MCancelObserveCallback(logService, client, versionedId); var rpcCallback = new RpcCancelObserveCallback(transportService, client, requestMsg, mainCallback); downlinkHandler.sendCancelObserveRequest(client, downlink, rpcCallback); } private void sendDeleteRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { TbLwM2MDeleteRequest downlink = TbLwM2MDeleteRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MDeleteCallback(uplinkHandler, client, versionedId); + var mainCallback = new TbLwM2MDeleteCallback(logService, client, versionedId); var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); downlinkHandler.sendDeleteRequest(client, downlink, rpcCallback); } private void sendCancelAllObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { TbLwM2MCancelAllRequest downlink = TbLwM2MCancelAllRequest.builder().timeout(this.config.getTimeout()).build(); - var mainCallback = new TbLwM2MCancelAllObserveCallback(uplinkHandler, client); + var mainCallback = new TbLwM2MCancelAllObserveCallback(logService, client); var rpcCallback = new RpcCancelAllObserveCallback(transportService, client, requestMsg, mainCallback); downlinkHandler.sendCancelAllRequest(client, downlink, rpcCallback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 61c4b077d9..5b61ffc768 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -44,8 +44,6 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.id.OtaPackageId; -import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -66,9 +64,9 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientState; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MClientStateException; 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.LwM2mFwSwUpdate; import org.thingsboard.server.transport.lwm2m.server.client.ParametersAnalyzeResult; import org.thingsboard.server.transport.lwm2m.server.client.ResultsAddKeyValueProto; +import org.thingsboard.server.transport.lwm2m.server.common.LwM2MExecutorAwareService; import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; @@ -82,12 +80,14 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadCallbac import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest; +import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -105,9 +105,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_3_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_DELIVERY_METHOD; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID; @@ -115,22 +113,17 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.F import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; @Slf4j @Service @TbLwM2mTransportComponent -public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { +public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService implements LwM2mUplinkMsgHandler { - private ExecutorService registrationExecutor; - private ExecutorService updateRegistrationExecutor; - private ExecutorService unRegistrationExecutor; public LwM2mValueConverterImpl converter; private final TransportService transportService; @@ -138,6 +131,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private final LwM2MAttributesService attributesService; private final LwM2MOtaUpdateService otaService; public final LwM2MTransportServerConfig config; + private final LwM2MTelemetryLogService logService; public final OtaPackageDataCache otaPackageDataCache; public final LwM2mTransportServerHelper helper; private final TbLwM2MDtlsSessionStore sessionStore; @@ -148,8 +142,10 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { public final Map firmwareUpdateState; public DefaultLwM2MUplinkMsgHandler(TransportService transportService, - LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, + LwM2MTransportServerConfig config, + LwM2mTransportServerHelper helper, LwM2mClientContext clientContext, + LwM2MTelemetryLogService logService, @Lazy LwM2MOtaUpdateService otaService, @Lazy LwM2MAttributesService attributesService, @Lazy LwM2MRpcRequestHandler rpcHandler, @@ -162,6 +158,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { this.config = config; this.helper = helper; this.clientContext = clientContext; + this.logService = logService; this.rpcHandler = rpcHandler; this.defaultLwM2MDownlinkMsgHandler = defaultLwM2MDownlinkMsgHandler; this.otaPackageDataCache = otaPackageDataCache; @@ -171,14 +168,27 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } @PostConstruct - public void initAttributes() { + public void init() { + super.init(); 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(); } + @PreDestroy + public void destroy() { + super.destroy(); + } + + @Override + protected String getExecutorName() { + return "LwM2M uplink"; + } + + @Override + protected int getExecutorSize() { + return config.getUplinkPoolSize(); + } + /** * Start registration device * Create session: Map, LwM2MClient> @@ -194,7 +204,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param previousObservations - may be null */ public void onRegistered(Registration registration, Collection previousObservations) { - registrationExecutor.submit(() -> { + executor.submit(() -> { LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); try { log.warn("[{}] [{{}] Client: create after Registration", registration.getEndpoint(), registration.getId()); @@ -204,7 +214,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { log.info("[{}] Closing old session: {}", registration.getEndpoint(), new UUID(oldSessionInfo.get().getSessionIdMSB(), oldSessionInfo.get().getSessionIdLSB())); closeSession(oldSessionInfo.get()); } - this.logToTelemetry(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId()); + logService.log(lwM2MClient, LOG_LWM2M_INFO + ": Client registered with registration id: " + registration.getId()); SessionInfoProto sessionInfo = lwM2MClient.getSession(); transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, attributesService, rpcHandler, sessionInfo)); log.warn("40) sessionId [{}] Registering rpc subscription after Registration client", new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); @@ -215,8 +225,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { .setSubscribeToRPC(TransportProtos.SubscribeToRPCMsg.newBuilder().setSessionType(TransportProtos.SessionType.ASYNC).build()) .build(); transportService.process(msg, null); - this.getInfoFirmwareUpdate(lwM2MClient); - this.getInfoSoftwareUpdate(lwM2MClient); this.initClientTelemetry(lwM2MClient); this.initAttributes(lwM2MClient); otaService.init(lwM2MClient); @@ -229,11 +237,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { // Race condition detected and the client was in progress of unregistration while new registration arrived. Let's try again. onRegistered(registration, previousObservations); } else { - this.logToTelemetry(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState()); + logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to invalid state: " + stateException.getState()); } } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable registration.", registration.getEndpoint(), t); - this.logToTelemetry(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to: " + t.getMessage()); + logService.log(lwM2MClient, LOG_LWM2M_WARN + ": Client registration failed due to: " + t.getMessage()); } }); } @@ -244,7 +252,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param registration - Registration LwM2M Client */ public void updatedReg(Registration registration) { - updateRegistrationExecutor.submit(() -> { + executor.submit(() -> { LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); try { log.warn("[{}] [{{}] Client: update after Registration", registration.getEndpoint(), registration.getId()); @@ -265,7 +273,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable update registration.", registration.getEndpoint(), t); - this.logToTelemetry(lwM2MClient, LOG_LWM2M_ERROR + String.format(": Client update Registration, %s", t.getMessage())); + logService.log(lwM2MClient, LOG_LWM2M_ERROR + String.format(": Client update Registration, %s", t.getMessage())); } }); } @@ -275,10 +283,10 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param observations - !!! Warn: if have not finishing unReg, then this operation will be finished on next Client`s connect */ public void unReg(Registration registration, Collection observations) { - unRegistrationExecutor.submit(() -> { + executor.submit(() -> { LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); try { - this.logToTelemetry(client, LOG_LWM2M_INFO + ": Client unRegistration"); + logService.log(client, LOG_LWM2M_INFO + ": Client unRegistration"); clientContext.unregister(client, registration); SessionInfoProto sessionInfo = client.getSession(); if (sessionInfo != null) { @@ -292,7 +300,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { log.info("[{}] delete registration: [{}] {}.", registration.getEndpoint(), stateException.getState(), stateException.getMessage()); } catch (Throwable t) { log.error("[{}] endpoint [{}] error Unable un registration.", registration.getEndpoint(), t); - this.logToTelemetry(client, LOG_LWM2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage())); + logService.log(client, LOG_LWM2M_ERROR + String.format(": Client Unable un Registration, %s", t.getMessage())); } }); } @@ -305,7 +313,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { @Override public void onSleepingDev(Registration registration) { log.info("[{}] [{}] Received endpoint Sleeping version event", registration.getId(), registration.getEndpoint()); - this.logToTelemetry(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is sleeping!"); + logService.log(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is sleeping!"); //TODO: associate endpointId with device information. } @@ -403,29 +411,10 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { @Override public void onAwakeDev(Registration registration) { log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); - this.logToTelemetry(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is awake!"); + logService.log(clientContext.getClientByEndpoint(registration.getEndpoint()), LOG_LWM2M_INFO + ": Client is awake!"); //TODO: associate endpointId with device information. } - /** - * @param logMsg - text msg - * @param registrationId - Id of Registration LwM2M Client - */ - @Override - public void logToTelemetry(String registrationId, String logMsg) { - logToTelemetry(clientContext.getClientByRegistrationId(registrationId), logMsg); - } - - @Override - public void logToTelemetry(LwM2mClient client, String logMsg) { - if (logMsg != null && client != null && client.getSession() != null) { - if (logMsg.length() > 1024) { - logMsg = logMsg.substring(0, 1024); - } - this.helper.sendParametersOnThingsboardTelemetry(this.helper.getKvStringtoThingsboard(LOG_LWM2M_TELEMETRY, logMsg), client.getSession()); - } - } - /** * #1 clientOnlyObserveAfterConnect == true * - Only Observe Request to the client marked as observe from the profile configuration. @@ -441,10 +430,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { Lwm2mDeviceProfileTransportConfiguration profile = clientContext.getProfile(lwM2MClient.getProfileId()); Set supportedObjects = clientContext.getSupportedIdVerInClient(lwM2MClient); if (supportedObjects != null && supportedObjects.size() > 0) { - if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == profile.getClientLwM2mSettings().getClientOnlyObserveAfterConnect()) { - // #2 - supportedObjects.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId)); - } // #1 this.sendReadRequests(lwM2MClient, profile, supportedObjects); this.sendObserveRequests(lwM2MClient, profile, supportedObjects); @@ -461,7 +446,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { CountDownLatch latch = new CountDownLatch(targetIds.size()); targetIds.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId, - new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, lwM2MClient, versionedId)))); + new TbLwM2MLatchCallback<>(latch, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId)))); try { latch.await(); } catch (InterruptedException e) { @@ -475,7 +460,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { CountDownLatch latch = new CountDownLatch(targetIds.size()); targetIds.forEach(targetId -> sendObserveRequest(lwM2MClient, targetId, - new TbLwM2MLatchCallback<>(latch, new TbLwM2MObserveCallback(this, lwM2MClient, targetId)))); + new TbLwM2MLatchCallback<>(latch, new TbLwM2MObserveCallback(this, logService, lwM2MClient, targetId)))); try { latch.await(); } catch (InterruptedException e) { @@ -501,11 +486,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private void sendDiscoverRequest(LwM2mClient lwM2MClient, String targetId) { TbLwM2MDiscoverRequest request = TbLwM2MDiscoverRequest.builder().versionedId(targetId).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, request, new TbLwM2MDiscoverCallback(this, lwM2MClient, targetId)); + defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, request, new TbLwM2MDiscoverCallback(logService, lwM2MClient, targetId)); } private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) { - sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, lwM2MClient, versionedId)); + sendReadRequest(lwM2MClient, versionedId, new TbLwM2MReadCallback(this, logService, lwM2MClient, versionedId)); } private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId, DownlinkRequestCallback callback) { @@ -514,7 +499,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } private void sendObserveRequest(LwM2mClient lwM2MClient, String versionedId) { - sendObserveRequest(lwM2MClient, versionedId, new TbLwM2MObserveCallback(this, lwM2MClient, versionedId)); + sendObserveRequest(lwM2MClient, versionedId, new TbLwM2MObserveCallback(this, logService, lwM2MClient, versionedId)); } private void sendObserveRequest(LwM2mClient lwM2MClient, String versionedId, DownlinkRequestCallback callback) { @@ -524,12 +509,12 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private void sendWriteAttributesRequest(LwM2mClient lwM2MClient, String targetId, ObjectAttributes params) { TbLwM2MWriteAttributesRequest request = TbLwM2MWriteAttributesRequest.builder().versionedId(targetId).attributes(params).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(lwM2MClient, request, new TbLwM2MWriteAttributesCallback(this, lwM2MClient, targetId)); + defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(lwM2MClient, request, new TbLwM2MWriteAttributesCallback(logService, lwM2MClient, targetId)); } private void sendCancelObserveRequest(String versionedId, LwM2mClient client) { TbLwM2MCancelObserveRequest request = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveCallback(this, client, versionedId)); + defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveCallback(logService, client, versionedId)); } private void updateObjectResourceValue(LwM2mClient client, LwM2mObject lwM2mObject, String pathIdVer) { @@ -565,9 +550,9 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (path.equals(convertObjectIdToVersionedId(FW_NAME_ID, registration))) { otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); } else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) { - otaService.onCurrentFirmwareVersionUpdate(lwM2MClient, (String) lwM2mResource.getValue()); + otaService.onCurrentFirmwareVersion3Update(lwM2MClient, (String) lwM2mResource.getValue()); } else if (path.equals(convertObjectIdToVersionedId(FW_5_VER_ID, registration))) { - otaService.onCurrentFirmwareVersionUpdate(lwM2MClient, (String) lwM2mResource.getValue()); + otaService.onCurrentFirmwareVersion5Update(lwM2MClient, (String) lwM2mResource.getValue()); } else if (path.equals(convertObjectIdToVersionedId(FW_STATE_ID, registration))) { otaService.onCurrentFirmwareStateUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); } else if (path.equals(convertObjectIdToVersionedId(FW_RESULT_ID, registration))) { @@ -919,93 +904,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { DonAsynchron.withCallback(attributesService.getSharedAttributes(lwM2MClient, keysToFetch), v -> attributesService.onAttributesUpdate(lwM2MClient, v), t -> log.error("[{}] Failed to get attributes", lwM2MClient.getEndpoint(), t), - registrationExecutor); - } - } - - public void getInfoFirmwareUpdate(LwM2mClient lwM2MClient) { - if (lwM2MClient.getRegistration().getSupportedVersion(FW_5_ID) != null) { - SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); - if (sessionInfo != null) { - DefaultLwM2MUplinkMsgHandler handler = this; - this.transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.FIRMWARE.name()), - new TransportServiceCallback<>() { - @Override - public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { - if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) - && response.getType().equals(OtaPackageType.FIRMWARE.name())) { - LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(DefaultLwM2MUplinkMsgHandler.this, clientContext); -// if (rpcRequest != null) { -// fwUpdate.setStateUpdate(INITIATED.name()); -// } - if (!FAILED.name().equals(fwUpdate.getStateUpdate())) { - log.warn("7) firmware start with ver: [{}]", response.getVersion()); -// fwUpdate.setRpcRequest(rpcRequest); - fwUpdate.setCurrentVersion(response.getVersion()); - fwUpdate.setCurrentTitle(response.getTitle()); - fwUpdate.setCurrentId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())); -// if (rpcRequest == null) { - fwUpdate.sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); -// } else { -// fwUpdate.writeFwSwWare(handler, defaultLwM2MDownlinkMsgHandler); -// } - } else { - String msgError = String.format("OtaPackage device: %s, version: %s, stateUpdate: %s", - lwM2MClient.getDeviceName(), response.getVersion(), fwUpdate.getStateUpdate()); - log.warn("7_1 [{}]", msgError); - } - } else { - String msgError = String.format("OtaPackage device: %s, responseStatus: %s", - lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); - log.trace(msgError); -// if (rpcRequest != null) { - //TODO: refactor -// sendErrorRpcResponse(rpcRequest, msgError, sessionInfo); -// } - } - } - - @Override - public void onError(Throwable e) { - log.trace("Failed to process firmwareUpdate ", e); - } - }); - } - } - } - - public void getInfoSoftwareUpdate(LwM2mClient lwM2MClient) { - if (lwM2MClient.getRegistration().getSupportedVersion(SW_ID) != null) { - SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); - if (sessionInfo != null) { - DefaultLwM2MUplinkMsgHandler handler = this; - transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.SOFTWARE.name()), - new TransportServiceCallback<>() { - @Override - public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { - if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) - && response.getType().equals(OtaPackageType.SOFTWARE.name())) { -// lwM2MClient.getSwUpdate().setRpcRequest(rpcRequest); - lwM2MClient.getSwUpdate().setCurrentVersion(response.getVersion()); - lwM2MClient.getSwUpdate().setCurrentTitle(response.getTitle()); - lwM2MClient.getSwUpdate().setCurrentId(new OtaPackageId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())).getId()); - lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); -// if (rpcRequest == null) { - lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); -// } else { -// lwM2MClient.getSwUpdate().writeFwSwWare(handler, defaultLwM2MDownlinkMsgHandler); -// } - } else { - log.trace("Software [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); - } - } - - @Override - public void onError(Throwable e) { - log.trace("Failed to process softwareUpdate ", e); - } - }); - } + executor); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java index 517db388e4..372daa7052 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java @@ -52,10 +52,6 @@ public interface LwM2mUplinkMsgHandler { void onAwakeDev(Registration registration); - void logToTelemetry(LwM2mClient client, String msg); - - void logToTelemetry(String registrationId, String msg); - void onWriteResponseOk(LwM2mClient client, String path, WriteRequest request); void onToTransportUpdateCredentials(TransportProtos.ToTransportUpdateCredentialsProto updateCredentials); diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index d81b28a5ff..d7e97231e0 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -142,12 +142,10 @@ transport: 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}" - registration_store_pool_size: "${LWM2M_REGISTRATION_STORE_POOL_SIZE:100}" + uplink_pool_size: "${LWM2M_UPLINK_POOL_SIZE:10}" + downlink_pool_size: "${LWM2M_DOWNLINK_POOL_SIZE:10}" + ota_pool_size: "${LWM2M_OTA_POOL_SIZE:10}" clean_period_in_sec: "${LWM2M_CLEAN_PERIOD_IN_SEC:2}" - 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}" From 8b285c8e77f5ff6fb9b88e68c4acc375fa268b5a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 15 Jun 2021 12:34:25 +0300 Subject: [PATCH 441/476] created OtaPackageTransportResource --- .../common/msg/session/FeatureType.java | 2 +- .../common/msg/session/SessionMsgType.java | 5 +- .../transport/coap/CoapTransportResource.java | 72 --------- .../transport/coap/CoapTransportService.java | 3 + .../coap/OtaPackageTransportResource.java | 144 ++++++++++++++++++ 5 files changed, 149 insertions(+), 77 deletions(-) create mode 100644 common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java 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 ae965e327a..9be2957401 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, SOFTWARE + ATTRIBUTES, TELEMETRY, RPC, CLAIM, PROVISION } 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 939197af80..5dbea04d59 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,10 +30,7 @@ public enum SessionMsgType { SESSION_OPEN, SESSION_CLOSE, - CLAIM_REQUEST(), - - GET_FIRMWARE_REQUEST, - GET_SOFTWARE_REQUEST; + CLAIM_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 33f122fa3f..95cdeada88 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 @@ -44,7 +44,6 @@ 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.ota.OtaPackageType; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.msg.session.SessionMsgType; @@ -139,10 +138,6 @@ 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 if (featureType.get() == FeatureType.SOFTWARE) { - processRequest(exchange, SessionMsgType.GET_SOFTWARE_REQUEST); } else { log.trace("Invalid feature type parameter"); exchange.respond(CoAP.ResponseCode.BAD_REQUEST); @@ -349,12 +344,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { coapTransportAdaptor.convertToGetAttributes(sessionId, request), new CoapNoOpCallback(exchange)); break; - case GET_FIRMWARE_REQUEST: - getOtaPackageCallback(sessionInfo, exchange, OtaPackageType.FIRMWARE); - break; - case GET_SOFTWARE_REQUEST: - getOtaPackageCallback(sessionInfo, exchange, OtaPackageType.SOFTWARE); - break; } } catch (AdaptorException e) { log.trace("[{}] Failed to decode message: ", sessionId, e); @@ -366,16 +355,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); } - private void getOtaPackageCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, OtaPackageType firmwareType) { - TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() - .setTenantIdMSB(sessionInfo.getTenantIdMSB()) - .setTenantIdLSB(sessionInfo.getTenantIdLSB()) - .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) - .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) - .setType(firmwareType.name()).build(); - transportContext.getTransportService().process(sessionInfo, requestMsg, new OtaPackageCallback(exchange)); - } - private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { tokenToObserveNotificationSeqMap.remove(token); return tokenToSessionInfoMap.remove(token); @@ -470,57 +449,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } } - private class OtaPackageCallback implements TransportServiceCallback { - private final CoapExchange exchange; - - OtaPackageCallback(CoapExchange exchange) { - this.exchange = exchange; - } - - @Override - public void onSuccess(TransportProtos.GetOtaPackageResponseMsg msg) { - String title = exchange.getQueryParameter("title"); - String version = exchange.getQueryParameter("version"); - if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { - String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString(); - if (msg.getTitle().equals(title) && msg.getVersion().equals(version)) { - 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); - exchange.respond(CoAP.ResponseCode.CONTENT, transportContext.getOtaPackageDataCache().get(firmwareId, chunkSize, chunk)); - } - else if (firmwareId != null) { - sendOtaData(exchange, firmwareId); - } 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 void sendOtaData(CoapExchange exchange, String firmwareId) { - Response response = new Response(CoAP.ResponseCode.CONTENT); - byte[] fwData = transportContext.getOtaPackageDataCache().get(firmwareId); - if (fwData != null && fwData.length > 0) { - response.setPayload(fwData); - if (exchange.getRequestOptions().getBlock2() != null) { - int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); - boolean moreFlag = fwData.length > chunkSize; - response.getOptions().setBlock2(chunkSize, moreFlag, 0); - } - exchange.respond(response); - } - } - private static class CoapSessionListener implements SessionMsgListener { private final CoapTransportResource coapTransportResource; 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 ce1618fe99..72be5e6f1e 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.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.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; import javax.annotation.PostConstruct; @@ -59,6 +60,8 @@ public class CoapTransportService implements TbTransportService { efento.add(efentoMeasurementsTransportResource); coapServer.add(api); coapServer.add(efento); + coapServer.add(new OtaPackageTransportResource(coapTransportContext, OtaPackageType.FIRMWARE)); + coapServer.add(new OtaPackageTransportResource(coapTransportContext, OtaPackageType.SOFTWARE)); log.info("CoAP transport started!"); } diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java new file mode 100644 index 0000000000..60e8f55352 --- /dev/null +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java @@ -0,0 +1,144 @@ +/** + * 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.coap; + +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.core.coap.Response; +import org.eclipse.californium.core.network.Exchange; +import org.eclipse.californium.core.server.resources.CoapExchange; +import org.eclipse.californium.core.server.resources.Resource; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.security.DeviceTokenCredentials; +import org.thingsboard.server.common.transport.TransportServiceCallback; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +@Slf4j +public class OtaPackageTransportResource extends AbstractCoapTransportResource { + private static final int ACCESS_TOKEN_POSITION = 2; + + private final OtaPackageType otaPackageType; + + public OtaPackageTransportResource(CoapTransportContext ctx, OtaPackageType otaPackageType) { + super(ctx, otaPackageType.getKeyPrefix()); + this.otaPackageType = otaPackageType; + } + + @Override + protected void processHandleGet(CoapExchange exchange) { + log.trace("Processing {}", exchange.advanced().getRequest()); + exchange.accept(); + Exchange advanced = exchange.advanced(); + Request request = advanced.getRequest(); + processAccessTokenRequest(exchange, request); + } + + @Override + protected void processHandlePost(CoapExchange exchange) { + exchange.respond(CoAP.ResponseCode.METHOD_NOT_ALLOWED); + } + + private void processAccessTokenRequest(CoapExchange exchange, Request request) { + Optional credentials = decodeCredentials(request); + if (credentials.isEmpty()) { + exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); + return; + } + transportService.process(DeviceTransportType.COAP, TransportProtos.ValidateDeviceTokenRequestMsg.newBuilder().setToken(credentials.get().getCredentialsId()).build(), + new CoapDeviceAuthCallback(transportContext, exchange, (sessionInfo, deviceProfile) -> { + getOtaPackageCallback(sessionInfo, exchange, otaPackageType); + })); + } + + private void getOtaPackageCallback(TransportProtos.SessionInfoProto sessionInfo, CoapExchange exchange, OtaPackageType firmwareType) { + TransportProtos.GetOtaPackageRequestMsg requestMsg = TransportProtos.GetOtaPackageRequestMsg.newBuilder() + .setTenantIdMSB(sessionInfo.getTenantIdMSB()) + .setTenantIdLSB(sessionInfo.getTenantIdLSB()) + .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) + .setDeviceIdLSB(sessionInfo.getDeviceIdLSB()) + .setType(firmwareType.name()).build(); + transportContext.getTransportService().process(sessionInfo, requestMsg, new OtaPackageCallback(exchange)); + } + + private Optional decodeCredentials(Request request) { + List uriPath = request.getOptions().getUriPath(); + if (uriPath.size() == ACCESS_TOKEN_POSITION) { + return Optional.of(new DeviceTokenCredentials(uriPath.get(ACCESS_TOKEN_POSITION - 1))); + } else { + return Optional.empty(); + } + } + + @Override + public Resource getChild(String name) { + return this; + } + + private class OtaPackageCallback implements TransportServiceCallback { + private final CoapExchange exchange; + + OtaPackageCallback(CoapExchange exchange) { + this.exchange = exchange; + } + + @Override + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg msg) { + String title = exchange.getQueryParameter("title"); + String version = exchange.getQueryParameter("version"); + if (msg.getResponseStatus().equals(TransportProtos.ResponseStatus.SUCCESS)) { + String firmwareId = new UUID(msg.getOtaPackageIdMSB(), msg.getOtaPackageIdLSB()).toString(); + if ((title == null || msg.getTitle().equals(title)) && (version == null || msg.getVersion().equals(version))) { + 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); + respondOtaPackage(exchange, transportContext.getOtaPackageDataCache().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 void respondOtaPackage(CoapExchange exchange, byte[] data) { + Response response = new Response(CoAP.ResponseCode.CONTENT); + if (data != null && data.length > 0) { + response.setPayload(data); + if (exchange.getRequestOptions().getBlock2() != null) { + int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); + boolean moreFlag = data.length > chunkSize; + response.getOptions().setBlock2(chunkSize, moreFlag, 0); + } + exchange.respond(response); + } + } + +} From 209c93064a2b6251ebf95accf08b4684d884b83b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 17 Jun 2021 19:36:15 +0300 Subject: [PATCH 442/476] Fix issue on LWM2M Security Store --- .../coap/OtaPackageTransportResource.java | 4 +-- .../server/LwM2mTransportCoapResource.java | 6 ++--- .../server/LwM2mTransportServerHelper.java | 26 ++----------------- .../server/store/TbLwM2mSecurityStore.java | 3 ++- 4 files changed, 9 insertions(+), 30 deletions(-) diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java index 60e8f55352..cb403c5e82 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java @@ -134,8 +134,8 @@ public class OtaPackageTransportResource extends AbstractCoapTransportResource { response.setPayload(data); if (exchange.getRequestOptions().getBlock2() != null) { int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); - boolean moreFlag = data.length > chunkSize; - response.getOptions().setBlock2(chunkSize, moreFlag, 0); + boolean lastFlag = data.length > chunkSize; + response.getOptions().setBlock2(chunkSize, lastFlag, 0); } exchange.respond(response); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java index 229ec1a08c..b996922ded 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java @@ -143,9 +143,9 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { response.setPayload(fwData); if (exchange.getRequestOptions().getBlock2() != null) { int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); - boolean moreFlag = fwData.length > chunkSize; - response.getOptions().setBlock2(chunkSize, moreFlag, 0); - log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, moreFlag); + boolean lastFlag = fwData.length > chunkSize; + response.getOptions().setBlock2(chunkSize, lastFlag, 0); + log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, lastFlag); } else { log.warn("92) with block1 Send currentId: [{}], length: [{}], ", currentId.toString(), fwData.length); 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 9e36d35076..a50e979b77 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 @@ -76,32 +76,11 @@ public class LwM2mTransportServerHelper { return TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) * 1000L + (atomicTs.getAndIncrement() % 1000); } - /** - * send to Thingsboard Attribute || Telemetry - * - * @param msg - JsonObject: [{name: value}] - * @return - dummyWriteReplace {\"targetIdVer\":\"/19_1.0/0/0\",\"value\":0082} - */ - private TransportServiceCallback getPubAckCallbackSendAttrTelemetry(final T msg) { - return new TransportServiceCallback<>() { - @Override - public void onSuccess(Void dummy) { - log.trace("Success to publish msg: {}, dummy: {}", msg, dummy); - } - - @Override - public void onError(Throwable e) { - log.trace("[{}] Failed to publish msg: {}", msg, e); - } - }; - } - public void sendParametersOnThingsboardAttribute(List result, SessionInfoProto sessionInfo) { PostAttributeMsg.Builder request = PostAttributeMsg.newBuilder(); request.addAllKv(result); PostAttributeMsg postAttributeMsg = request.build(); - TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postAttributeMsg); - context.getTransportService().process(sessionInfo, postAttributeMsg, this.getPubAckCallbackSendAttrTelemetry(call)); + context.getTransportService().process(sessionInfo, postAttributeMsg, TransportServiceCallback.EMPTY); } public void sendParametersOnThingsboardTelemetry(List result, SessionInfoProto sessionInfo) { @@ -111,8 +90,7 @@ public class LwM2mTransportServerHelper { builder.addAllKv(result); request.addTsKvList(builder.build()); PostTelemetryMsg postTelemetryMsg = request.build(); - TransportServiceCallback call = this.getPubAckCallbackSendAttrTelemetry(postTelemetryMsg); - context.getTransportService().process(sessionInfo, postTelemetryMsg, this.getPubAckCallbackSendAttrTelemetry(call)); + context.getTransportService().process(sessionInfo, postTelemetryMsg, TransportServiceCallback.EMPTY); } /** 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 index cbdb9faf07..293e68ec39 100644 --- 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 @@ -85,6 +85,7 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore { @Override public void remove(String endpoint) { - securityStore.remove(endpoint); + //TODO: Make sure we delay removal of security store from endpoint due to reg/unreg race condition. +// securityStore.remove(endpoint); } } From be3e97d3e161933b93fdc6affc300e7dae7d0a15 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 18 Jun 2021 12:24:31 +0300 Subject: [PATCH 443/476] Improvements for CoAP --- .../lwm2m/NoSecLwM2MIntegrationTest.java | 2 +- .../lwm2m/X509LwM2MIntegrationTest.java | 7 +++- .../coapserver/DefaultCoapServerService.java | 13 ++++++- .../coap/OtaPackageTransportResource.java | 37 +++++++++++++++++++ .../uplink/DefaultLwM2MUplinkMsgHandler.java | 1 + 5 files changed, 56 insertions(+), 4 deletions(-) diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index e94f432f64..d98a775c8c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java @@ -53,7 +53,7 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { private final int PORT = 5685; private final Security SECURITY = noSec("coap://localhost:" + PORT, 123); private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); - private final String ENDPOINT = "deviceAEndpoint"; + private final String ENDPOINT = "noSecEndpoint"; private Device createDevice() throws Exception { Device device = new Device(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java index a5efff968a..661f7c5474 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -19,6 +19,7 @@ import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.leshan.client.object.Security; import org.jetbrains.annotations.NotNull; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; @@ -74,11 +75,13 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { return device; } + //TODO: use different endpoints to isolate tests. + @Ignore() @Test public void testConnectAndObserveTelemetry() throws Exception { createDeviceProfile(TRANSPORT_CONFIGURATION); X509ClientCredentials credentials = new X509ClientCredentials(); - credentials.setEndpoint(endpoint); + credentials.setEndpoint(endpoint+1); Device device = createDevice(credentials); SingleEntityFilter sef = new SingleEntityFilter(); @@ -96,7 +99,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { wsClient.waitForReply(); wsClient.registerWaitForUpdate(); - LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint+1); Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); client.init(security, coapConfig); String msg = wsClient.waitForUpdate(); 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 93f4e76551..605952e456 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 @@ -36,6 +36,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME; + @Slf4j @Component @TbCoapServerComponent @@ -91,7 +93,16 @@ public class DefaultCoapServerService implements CoapServerService { InetAddress addr = InetAddress.getByName(coapServerContext.getHost()); InetSocketAddress sockAddr = new InetSocketAddress(addr, coapServerContext.getPort()); noSecCoapEndpointBuilder.setInetSocketAddress(sockAddr); - noSecCoapEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard()); + NetworkConfig networkConfig = new NetworkConfig(); + networkConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); + networkConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); + networkConfig.setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME); + networkConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024); + networkConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED"); + networkConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); + networkConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); + networkConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); + noSecCoapEndpointBuilder.setNetworkConfig(networkConfig); CoapEndpoint noSecCoapEndpoint = noSecCoapEndpointBuilder.build(); server.addEndpoint(noSecCoapEndpoint); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java index cb403c5e82..bc9fdd9282 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java @@ -20,8 +20,10 @@ import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Exchange; +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.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageType; @@ -41,6 +43,8 @@ public class OtaPackageTransportResource extends AbstractCoapTransportResource { public OtaPackageTransportResource(CoapTransportContext ctx, OtaPackageType otaPackageType) { super(ctx, otaPackageType.getKeyPrefix()); + this.setObservable(true); + this.addObserver(new OtaPackageTransportResource.CoapResourceObserver()); this.otaPackageType = otaPackageType; } @@ -135,10 +139,43 @@ public class OtaPackageTransportResource extends AbstractCoapTransportResource { if (exchange.getRequestOptions().getBlock2() != null) { int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); boolean lastFlag = data.length > chunkSize; + response.getOptions().setUriPath(exchange.getRequestOptions().getUriPathString()); response.getOptions().setBlock2(chunkSize, lastFlag, 0); } exchange.respond(response); } } + public class CoapResourceObserver implements ResourceObserver { + @Override + public void changedName(String old) { + + } + + @Override + public void changedPath(String old) { + + } + + @Override + public void addedChild(Resource child) { + + } + + @Override + public void removedChild(Resource child) { + + } + + @Override + public void addedObserveRelation(ObserveRelation relation) { + + } + + @Override + public void removedObserveRelation(ObserveRelation relation) { + + } + } + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 5b61ffc768..cd9c311b54 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -256,6 +256,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); try { log.warn("[{}] [{{}] Client: update after Registration", registration.getEndpoint(), registration.getId()); + logService.log(lwM2MClient, String.format("[%s][%s] Updated registration.", registration.getId(), registration.getSocketAddress())); clientContext.updateRegistration(lwM2MClient, registration); TransportProtos.SessionInfoProto sessionInfo = lwM2MClient.getSession(); this.reportActivityAndRegister(sessionInfo); From fe3378eab125b6f59ae316890f3ed4384b16f114 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 18 Jun 2021 12:58:38 +0300 Subject: [PATCH 444/476] UI: Add SNMP device transport configuration --- .../SnmpDeviceTransportConfiguration.java | 9 +- ...-profile-communication-config.component.ts | 4 +- ...snmp-device-profile-mapping.component.scss | 3 + ...ofile-transport-configuration.component.ts | 2 +- .../device/data/device-data.component.ts | 36 +++++- ...evice-transport-configuration.component.ts | 44 +++++-- ...ice-transport-configuration.component.html | 121 +++++++++++++++++- ...evice-transport-configuration.component.ts | 112 +++++++++++++--- ui-ngx/src/app/shared/models/device.models.ts | 60 ++++++++- .../assets/locale/locale.constant-en_US.json | 25 +++- 10 files changed, 366 insertions(+), 50 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 a7bc143d81..d377ede985 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 @@ -51,6 +51,13 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur private String privacyPassphrase; private String engineId; + public SnmpDeviceTransportConfiguration() { + this.host = "localhost"; + this.port = 161; + this.protocolVersion = SnmpProtocolVersion.V2C; + this.community = "public"; + } + @Override public DeviceTransportType getType() { return DeviceTransportType.SNMP; @@ -76,7 +83,7 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName) && contextName != null && authenticationProtocol != null && StringUtils.isNotBlank(authenticationPassphrase) - && privacyProtocol != null && privacyPassphrase != null && engineId != null; + && privacyProtocol != null && StringUtils.isNotBlank(privacyPassphrase) && engineId != null; break; } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts index d683d46880..7af2140efe 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-communication-config.component.ts @@ -179,7 +179,7 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On if (isUndefinedOrNull(value)) { value = { spec: this.getFirstUnusedSeverity(), - queryingFrequencyMs: 0, + queryingFrequencyMs: 5000, mappings: null }; } @@ -196,7 +196,7 @@ export class SnmpDeviceProfileCommunicationConfigComponent implements OnInit, On ).subscribe(spec => { if (this.isShowFrequency(spec)) { form.addControl('queryingFrequencyMs', - this.fb.control(0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')])); + this.fb.control(5000, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')])); } else { form.removeControl('queryingFrequencyMs'); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss index 94cf2290ac..5972aee722 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-mapping.component.scss @@ -40,6 +40,9 @@ .mat-form-field-underline { bottom: 0; } + .mat-form-field-subscript-wrapper{ + margin-top: 1.8em; + } } } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.ts index a384acbae3..400199ef9c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/snpm/snmp-device-profile-transport-configuration.component.ts @@ -86,7 +86,7 @@ export class SnmpDeviceProfileTransportConfigurationComponent implements OnInit, ngOnInit(): void { this.snmpDeviceProfileTransportConfigurationFormGroup = this.fb.group({ - timeoutMs: [0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], + timeoutMs: [500, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], retries: [0, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], communicationConfigs: [null, Validators.required], }); diff --git a/ui-ngx/src/app/modules/home/pages/device/data/device-data.component.ts b/ui-ngx/src/app/modules/home/pages/device/data/device-data.component.ts index 1a510692db..a175764d89 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/device-data.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/data/device-data.component.ts @@ -15,7 +15,16 @@ /// import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + Validators +} from '@angular/forms'; import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; @@ -29,13 +38,20 @@ import { selector: 'tb-device-data', templateUrl: './device-data.component.html', styleUrls: [], - providers: [{ - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => DeviceDataComponent), - multi: true - }] + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceDataComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DeviceDataComponent), + multi: true + }, + ] }) -export class DeviceDataComponent implements ControlValueAccessor, OnInit { +export class DeviceDataComponent implements ControlValueAccessor, OnInit, Validator { deviceDataFormGroup: FormGroup; @@ -97,6 +113,12 @@ export class DeviceDataComponent implements ControlValueAccessor, OnInit { this.deviceDataFormGroup.patchValue({transportConfiguration: value?.transportConfiguration}, {emitEvent: false}); } + validate(): ValidationErrors | null { + return this.deviceDataFormGroup.valid ? null : { + deviceDataForm: false + }; + } + private updateModel() { let deviceData: DeviceData = null; if (this.deviceDataFormGroup.valid) { diff --git a/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.ts index 0e9d7e24a9..82359232a9 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/pages/device/data/device-transport-configuration.component.ts @@ -15,27 +15,39 @@ /// import { Component, forwardRef, Input, OnInit } from '@angular/core'; -import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + 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 -} from '@shared/models/device.models'; +import { DeviceTransportConfiguration, DeviceTransportType } from '@shared/models/device.models'; import { deepClone } from '@core/utils'; @Component({ selector: 'tb-device-transport-configuration', templateUrl: './device-transport-configuration.component.html', styleUrls: [], - providers: [{ - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => DeviceTransportConfigurationComponent), - multi: true - }] + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DeviceTransportConfigurationComponent), + multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => DeviceTransportConfigurationComponent), + multi: true + }] }) -export class DeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class DeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit, Validator { deviceTransportType = DeviceTransportType; @@ -92,7 +104,15 @@ export class DeviceTransportConfigurationComponent implements ControlValueAccess if (configuration) { delete configuration.type; } - this.deviceTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + setTimeout(() => { + this.deviceTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + }, 0); + } + + validate(): ValidationErrors | null { + return this.deviceTransportConfigurationFormGroup.valid ? null : { + deviceTransportConfiguration: false + }; } private updateModel() { 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 index fc9f615db9..dadc98425e 100644 --- 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 @@ -15,10 +15,119 @@ limitations under the License. --> -

- - + +
+ + device-profile.snmp.host + + + {{ 'device-profile.snmp.host-required' | translate }} + + + + device-profile.snmp.port + + + {{ 'device-profile.snmp.port-required' | translate }} + + + {{ 'device-profile.snmp.port-format' | translate }} + + +
+ + device-profile.snmp.protocol-version + + + {{ snmpDeviceProtocolVersion | lowercase }} + + + + {{ 'device-profile.snmp.protocol-version-required' | translate }} + + +
+ + device-profile.snmp.community + + + {{ 'device-profile.snmp.community-required' | translate }} + + +
+
+
+ + device-profile.snmp.user-name + + + {{ 'device-profile.snmp.user-name-required' | translate }} + + + + device-profile.snmp.security-name + + + {{ 'device-profile.snmp.security-name-required' | translate }} + + +
+
+ + device-profile.snmp.authentication-protocol + + + {{ snmpAuthenticationProtocolTranslation.get(snmpAuthenticationProtocol) }} + + + + {{ 'device-profile.snmp.authentication-protocol-required' | translate }} + + + + device-profile.snmp.authentication-passphrase + + + {{ 'device-profile.snmp.authentication-passphrase-required' | translate }} + + +
+
+ + device-profile.snmp.privacy-protocol + + + {{ snmpPrivacyProtocolTranslation.get(snmpPrivacyProtocol) }} + + + + {{ 'device-profile.snmp.privacy-protocol-required' | translate }} + + + + device-profile.snmp.privacy-passphrase + + + {{ 'device-profile.snmp.privacy-passphrase-required' | translate }} + + +
+
+ + device-profile.snmp.context-name + + + + device-profile.snmp.engine-id + + +
+
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 index 165a2906f4..f35d538bfa 100644 --- 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 @@ -14,31 +14,57 @@ /// 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 { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { + ControlValueAccessor, + FormBuilder, + FormGroup, + NG_VALIDATORS, + NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, + 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 + SnmpAuthenticationProtocol, + SnmpAuthenticationProtocolTranslationMap, + SnmpDeviceProtocolVersion, + SnmpDeviceTransportConfiguration, + SnmpPrivacyProtocol, + SnmpPrivacyProtocolTranslationMap } from '@shared/models/device.models'; +import { isDefinedAndNotNull } from '@core/utils'; @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 - }] + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => SnmpDeviceTransportConfigurationComponent), + multi: true + }, { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => SnmpDeviceTransportConfigurationComponent), + multi: true + }] }) -export class SnmpDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit { +export class SnmpDeviceTransportConfigurationComponent implements ControlValueAccessor, OnInit, Validator { snmpDeviceTransportConfigurationFormGroup: FormGroup; + snmpDeviceProtocolVersions = Object.values(SnmpDeviceProtocolVersion); + snmpAuthenticationProtocols = Object.values(SnmpAuthenticationProtocol); + snmpAuthenticationProtocolTranslation = SnmpAuthenticationProtocolTranslationMap; + snmpPrivacyProtocols = Object.values(SnmpPrivacyProtocol); + snmpPrivacyProtocolTranslation = SnmpPrivacyProtocolTranslationMap; + private requiredValue: boolean; get required(): boolean { @@ -53,8 +79,7 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc @Input() disabled: boolean; - private propagateChange = (v: any) => { - }; + private propagateChange = (v: any) => { }; constructor(private store: Store, private fb: FormBuilder) { @@ -69,13 +94,33 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc ngOnInit() { this.snmpDeviceTransportConfigurationFormGroup = this.fb.group({ - configuration: [null, Validators.required] + host: ['', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]], + port: [null, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], + protocolVersion: [SnmpDeviceProtocolVersion.V2C, Validators.required], + community: ['public', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]], + username: ['', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]], + securityName: ['public', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]], + contextName: [null], + authenticationProtocol: [SnmpAuthenticationProtocol.SHA_512, Validators.required], + authenticationPassphrase: ['', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]], + privacyProtocol: [SnmpPrivacyProtocol.DES, Validators.required], + privacyPassphrase: ['', [Validators.required, Validators.pattern('(.|\\s)*\\S(.|\\s)*')]], + engineId: [''] + }); + this.snmpDeviceTransportConfigurationFormGroup.get('protocolVersion').valueChanges.subscribe((protocol: SnmpDeviceProtocolVersion) => { + this.updateDisabledFormValue(protocol); }); this.snmpDeviceTransportConfigurationFormGroup.valueChanges.subscribe(() => { this.updateModel(); }); } + validate(): ValidationErrors | null { + return this.snmpDeviceTransportConfigurationFormGroup.valid ? null : { + snmpDeviceTransportConfiguration: false + }; + } + setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { @@ -86,13 +131,46 @@ export class SnmpDeviceTransportConfigurationComponent implements ControlValueAc } writeValue(value: SnmpDeviceTransportConfiguration | null): void { - this.snmpDeviceTransportConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + if (isDefinedAndNotNull(value)) { + this.snmpDeviceTransportConfigurationFormGroup.patchValue(value, {emitEvent: false}); + if (this.snmpDeviceTransportConfigurationFormGroup.enabled) { + this.updateDisabledFormValue(value.protocolVersion || SnmpDeviceProtocolVersion.V2C); + } + } + } + + isV3protocolVersion(): boolean { + return this.snmpDeviceTransportConfigurationFormGroup.get('protocolVersion').value === SnmpDeviceProtocolVersion.V3; + } + + private updateDisabledFormValue(protocol: SnmpDeviceProtocolVersion) { + if (protocol === SnmpDeviceProtocolVersion.V3) { + this.snmpDeviceTransportConfigurationFormGroup.get('community').disable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('username').enable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('securityName').enable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('contextName').enable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('authenticationProtocol').enable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('authenticationPassphrase').enable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('privacyProtocol').enable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('privacyPassphrase').enable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('engineId').enable({emitEvent: false}); + } else { + this.snmpDeviceTransportConfigurationFormGroup.get('community').enable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('username').disable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('securityName').disable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('contextName').disable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('authenticationProtocol').disable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('authenticationPassphrase').disable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('privacyProtocol').disable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('privacyPassphrase').disable({emitEvent: false}); + this.snmpDeviceTransportConfigurationFormGroup.get('engineId').disable({emitEvent: false}); + } } private updateModel() { let configuration: DeviceTransportConfiguration = null; if (this.snmpDeviceTransportConfigurationFormGroup.valid) { - configuration = this.snmpDeviceTransportConfigurationFormGroup.getRawValue().configuration; + configuration = this.snmpDeviceTransportConfigurationFormGroup.value; configuration.type = DeviceTransportType.SNMP; } this.propagateChange(configuration); diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index 568faeb452..a766c3a6c3 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -362,7 +362,7 @@ export function createDeviceProfileTransportConfiguration(type: DeviceTransportT break; case DeviceTransportType.SNMP: const snmpTransportConfiguration: SnmpDeviceProfileTransportConfiguration = { - timeoutMs: 0, + timeoutMs: 500, retries: 0, communicationConfigs: null }; @@ -394,7 +394,12 @@ export function createDeviceTransportConfiguration(type: DeviceTransportType): D transportConfiguration = {...lwm2mTransportConfiguration, type: DeviceTransportType.LWM2M}; break; case DeviceTransportType.SNMP: - const snmpTransportConfiguration: SnmpDeviceTransportConfiguration = {}; + const snmpTransportConfiguration: SnmpDeviceTransportConfiguration = { + host: 'localhost', + port: 161, + protocolVersion: SnmpDeviceProtocolVersion.V2C, + community: 'public' + }; transportConfiguration = {...snmpTransportConfiguration, type: DeviceTransportType.SNMP}; break; } @@ -572,8 +577,57 @@ export interface Lwm2mDeviceTransportConfiguration { [key: string]: any; } +export enum SnmpDeviceProtocolVersion { + V1 = 'V1', + V2C = 'V2C', + V3 = 'V3' +} + +export enum SnmpAuthenticationProtocol { + SHA_1 = 'SHA_1', + SHA_224 = 'SHA_224', + SHA_256 = 'SHA_256', + SHA_384 = 'SHA_384', + SHA_512 = 'SHA_512', + MD5 = 'MD%' +} + +export const SnmpAuthenticationProtocolTranslationMap = new Map([ + [SnmpAuthenticationProtocol.SHA_1, 'SHA-1'], + [SnmpAuthenticationProtocol.SHA_224, 'SHA-224'], + [SnmpAuthenticationProtocol.SHA_256, 'SHA-256'], + [SnmpAuthenticationProtocol.SHA_384, 'SHA-384'], + [SnmpAuthenticationProtocol.SHA_512, 'SHA-512'], + [SnmpAuthenticationProtocol.MD5, 'MD5'] +]); + +export enum SnmpPrivacyProtocol { + DES = 'DES', + AES_128 = 'AES_128', + AES_192 = 'AES_192', + AES_256 = 'AES_256' +} + +export const SnmpPrivacyProtocolTranslationMap = new Map([ + [SnmpPrivacyProtocol.DES, 'DES'], + [SnmpPrivacyProtocol.AES_128, 'AES-128'], + [SnmpPrivacyProtocol.AES_192, 'AES-192'], + [SnmpPrivacyProtocol.AES_256, 'AES-256'], +]); + export interface SnmpDeviceTransportConfiguration { - [key: string]: any; + host?: string; + port?: number; + protocolVersion?: SnmpDeviceProtocolVersion; + community?: string; + username?: string; + securityName?: string; + contextName?: string; + authenticationProtocol?: SnmpAuthenticationProtocol; + authenticationPassphrase?: string; + privacyProtocol?: SnmpPrivacyProtocol; + privacyPassphrase?: string; + engineId?: string; } export type DeviceTransportConfigurations = DefaultDeviceTransportConfiguration & 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 95dc0c2d72..ff07be44da 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1301,16 +1301,35 @@ "snmp": { "add-communication-config": "Add communication config", "add-mapping": "Add mapping", + "authentication-passphrase": "Authentication passphrase", + "authentication-passphrase-required": "Authentication passphrase is required.", + "authentication-protocol": "Authentication protocol", + "authentication-protocol-required": "Authentication protocol is required.", "communication-configs": "Communication configs", + "community": "Community string", + "community-required": "Community string is required.", + "context-name": "Context name", "data-key": "Data key", "data-key-required": "Data key is required.", "data-type": "Data type", "data-type-required": "Data type is required.", + "engine-id": "Engine ID", + "host": "Host", + "host-required": "Host is required.", "oid": "OID", "oid-pattern": "Invalid OID format.", "oid-required": "OID is required.", "please-add-communication-config": "Please add communication config", "please-add-mapping-config": "Please add mapping config", + "port": "Port", + "port-format": "Invalid port format.", + "port-required": "Port is required.", + "privacy-passphrase": "Privacy passphrase", + "privacy-passphrase-required": "Privacy passphrase is required.", + "privacy-protocol": "Privacy protocol", + "privacy-protocol-required": "Privacy protocol is required.", + "protocol-version": "Protocol version", + "protocol-version-required": "Protocol version is required.", "querying-frequency": "Querying frequency, ms", "querying-frequency-invalid-format": "Querying frequency must be a positive integer.", "querying-frequency-required": "Querying frequency is required.", @@ -1319,9 +1338,13 @@ "retries-required": "Retries is required.", "scope": "Scope", "scope-required": "Scope is required.", + "security-name": "Security name", + "security-name-required": "Security name is required.", "timeout-ms": "Timeout, ms", "timeout-ms-invalid-format": "Timeout must be a positive integer.", - "timeout-ms-required": "Timeout is required." + "timeout-ms-required": "Timeout is required.", + "user-name": "User name", + "user-name-required": "User name is required." } }, "dialog": { From e3c523cc6454db8a6b10aa63b1c542444028a45e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 18 Jun 2021 14:33:31 +0300 Subject: [PATCH 445/476] Write replace command fix --- .../lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java | 2 +- .../server/transport/lwm2m/server/rpc/IdOrKeyRequest.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index 5de223fa2f..0432a5b742 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -206,7 +206,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } private void sendWriteReplaceRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { - RpcWriteUpdateRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteUpdateRequest.class); + RpcWriteReplaceRequest requestBody = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteReplaceRequest.class); TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) .value(requestBody.getValue()) .timeout(this.config.getTimeout()).build(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java index 1055b01232..77d8cd3ea1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java @@ -15,9 +15,11 @@ */ package org.thingsboard.server.transport.lwm2m.server.rpc; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; @Data +@JsonIgnoreProperties(ignoreUnknown = true) public class IdOrKeyRequest { private String key; From e470a032a9f2fa12d949f6ed83df3ba7ef39e58b Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 18 Jun 2021 17:21:35 +0300 Subject: [PATCH 446/476] improved countByDeviceProfileAndEmptyOtaPackage api --- .../server/controller/DeviceController.java | 10 ++++++---- .../java/org/thingsboard/rest/client/RestClient.java | 2 +- ui-ngx/src/app/core/http/ota-package.service.ts | 2 +- 3 files changed, 8 insertions(+), 6 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 b13393515c..5c7df50d09 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -782,15 +782,17 @@ public class DeviceController extends BaseController { } @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") - @RequestMapping(value = "/devices/count/{otaPackageType}", method = RequestMethod.GET) + @RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET) @ResponseBody - public Long countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, - @RequestParam String deviceProfileId) throws ThingsboardException { + public Long countByDeviceProfileAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, + @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { checkParameter("OtaPackageType", otaPackageType); checkParameter("DeviceProfileId", deviceProfileId); try { return deviceService.countDevicesByTenantIdAndDeviceProfileIdAndEmptyOtaPackage( - getCurrentUser().getTenantId(), new DeviceProfileId(UUID.fromString(deviceProfileId)), OtaPackageType.valueOf(otaPackageType)); + getTenantId(), + new DeviceProfileId(UUID.fromString(deviceProfileId)), + OtaPackageType.valueOf(otaPackageType)); } catch (Exception e) { throw handleException(e); } 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 b716935b2a..6fbf28f24e 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 @@ -1257,7 +1257,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { params.put("deviceProfileId", deviceProfileId.getId().toString()); return restTemplate.exchange( - baseURL + "/api/devices/count/{otaPackageType}?deviceProfileId={deviceProfileId}", + baseURL + "/api/devices/count/{otaPackageType}/{deviceProfileId}", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference() { diff --git a/ui-ngx/src/app/core/http/ota-package.service.ts b/ui-ngx/src/app/core/http/ota-package.service.ts index 1f9a36882a..a806751825 100644 --- a/ui-ngx/src/app/core/http/ota-package.service.ts +++ b/ui-ngx/src/app/core/http/ota-package.service.ts @@ -133,7 +133,7 @@ export class OtaPackageService { } public countUpdateDeviceAfterChangePackage(type: OtaUpdateType, entityId: EntityId, config?: RequestConfig): Observable { - return this.http.get(`/api/devices/count/${type}?deviceProfileId=${entityId.id}`, defaultHttpOptionsFromConfig(config)); + return this.http.get(`/api/devices/count/${type}/${entityId.id}`, defaultHttpOptionsFromConfig(config)); } public confirmDialogUpdatePackage(entity: BaseData&OtaPagesIds, From 5b998717e79f7fcde249b9a0a3686a2c0f5be68e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 Jun 2021 18:00:12 +0300 Subject: [PATCH 447/476] Remove invalid test --- .../CachedAttributesServiceTest.java | 88 ------------------- 1 file changed, 88 deletions(-) delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java diff --git a/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java deleted file mode 100644 index af980a6301..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL - * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. - * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. - * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. - */ -package org.thingsboard.server.dao.attributes; - -import com.google.common.util.concurrent.MoreExecutors; -import org.junit.Test; -import org.thingsboard.server.dao.cache.CacheConfiguration; -import org.thingsboard.server.dao.cache.CacheExecutorService; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.BDDMockito.willCallRealMethod; -import static org.mockito.Mockito.mock; - -public class CachedAttributesServiceTest { - - public static final String REDIS = "redis"; - - @Test - public void givenLocalCacheTypeName_whenEquals_thenOK() { - assertThat(CachedAttributesService.LOCAL_CACHE_TYPE, is("caffeine")); - } - - @Test - public void givenCacheType_whenGetExecutor_thenDirectExecutor() { - CachedAttributesService cachedAttributesService = mock(CachedAttributesService.class); - CacheExecutorService cacheExecutorService = mock(CacheExecutorService.class); - willCallRealMethod().given(cachedAttributesService).getExecutor(any(), any()); - - assertThat(cachedAttributesService.getExecutor(null, cacheExecutorService), is(MoreExecutors.directExecutor())); - - CacheConfiguration cacheConfiguration = new CacheConfiguration(); - cacheConfiguration.setType(null); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); - - cacheConfiguration.setType(""); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); - - cacheConfiguration.setType(CachedAttributesService.LOCAL_CACHE_TYPE); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); - - } - - @Test - public void givenCacheType_whenGetExecutor_thenReturnCacheExecutorService() { - CachedAttributesService cachedAttributesService = mock(CachedAttributesService.class); - CacheExecutorService cacheExecutorService = mock(CacheExecutorService.class); - willCallRealMethod().given(cachedAttributesService).getExecutor(any(CacheConfiguration.class), any(CacheExecutorService.class)); - - CacheConfiguration cacheConfiguration = new CacheConfiguration(); - cacheConfiguration.setType(REDIS); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(cacheExecutorService)); - - cacheConfiguration.setType("unknownCacheType"); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(cacheExecutorService)); - - } - -} \ No newline at end of file From d64a0c382d7bac67224db60879ce640e8d3bd59a Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 Jun 2021 18:22:48 +0300 Subject: [PATCH 448/476] Fix test properties --- dao/src/test/resources/application-test.properties | 1 + 1 file changed, 1 insertion(+) diff --git a/dao/src/test/resources/application-test.properties b/dao/src/test/resources/application-test.properties index d7a960471b..a257c8fbce 100644 --- a/dao/src/test/resources/application-test.properties +++ b/dao/src/test/resources/application-test.properties @@ -10,6 +10,7 @@ audit-log.default_query_period=30 audit-log.sink.type=none cache.type=caffeine +cache.maximumPoolSize=16 #cache.type=redis caffeine.specs.relations.timeToLiveInMinutes=1440 From a7a7c801f2ea59f72cec1c92007fb9c0ad6d4804 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 18 Jun 2021 18:31:52 +0300 Subject: [PATCH 449/476] Fix license header --- .../dao/cache/CacheExecutorService.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java index ca00e41d79..9f0f54bb46 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/cache/CacheExecutorService.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * 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 * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * 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.cache; From c03893fb570ae659e41493f4801d62463207d956 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Sat, 19 Jun 2021 16:58:32 +0300 Subject: [PATCH 450/476] LWM2M: fix bug update FwUrl in client after change in profile --- .../coapserver/DefaultCoapServerService.java | 18 +++++++++++- .../transport/coap/CoapTransportService.java | 13 ++++++++- .../coap/OtaPackageTransportResource.java | 15 ++++++++-- ...LwM2mCredentialsSecurityInfoValidator.java | 3 ++ .../server/LwM2mTransportCoapResource.java | 4 +-- .../ota/DefaultLwM2MOtaUpdateService.java | 29 +++++++++++++++++-- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 8 +++-- .../server/ota/LwM2MOtaUpdateService.java | 4 +++ .../uplink/DefaultLwM2MUplinkMsgHandler.java | 27 +++++++++++++++-- ...ile-transport-configuration.component.html | 4 +-- ...ofile-transport-configuration.component.ts | 6 ++-- .../lwm2m/lwm2m-profile-config.models.ts | 4 +-- 12 files changed, 112 insertions(+), 23 deletions(-) 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 93f4e76551..211ccecc3e 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 @@ -36,6 +36,8 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME; + @Slf4j @Component @TbCoapServerComponent @@ -91,7 +93,21 @@ public class DefaultCoapServerService implements CoapServerService { InetAddress addr = InetAddress.getByName(coapServerContext.getHost()); InetSocketAddress sockAddr = new InetSocketAddress(addr, coapServerContext.getPort()); noSecCoapEndpointBuilder.setInetSocketAddress(sockAddr); - noSecCoapEndpointBuilder.setNetworkConfig(NetworkConfig.getStandard()); + + NetworkConfig coapConfig = new NetworkConfig(); + coapConfig.setInt(NetworkConfig.Keys.COAP_PORT, 5683); + coapConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT, 5684); + coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); + coapConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); + coapConfig.setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME); + coapConfig.setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024); + coapConfig.setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED"); + coapConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); + coapConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); + + coapConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10); + + noSecCoapEndpointBuilder.setNetworkConfig(coapConfig); CoapEndpoint noSecCoapEndpoint = noSecCoapEndpointBuilder.build(); server.addEndpoint(noSecCoapEndpoint); 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 72be5e6f1e..a5adc36d7e 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,11 +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.config.NetworkConfig; import org.springframework.beans.factory.annotation.Autowired; 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.common.data.TbTransportService; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.coap.efento.CoapEfentoTransportResource; @@ -30,6 +31,8 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.net.UnknownHostException; +import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME; + @Service("CoapTransportService") @TbCoapServerComponent @Slf4j @@ -52,6 +55,14 @@ public class CoapTransportService implements TbTransportService { public void init() throws UnknownHostException { log.info("Starting CoAP transport..."); coapServer = coapServerService.getCoapServer(); + coapServer.getConfig().setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); + coapServer.getConfig().setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); + coapServer.getConfig().setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME); + coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024); + coapServer.getConfig().setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED"); + coapServer.getConfig().setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); + coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); + coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10); CoapResource api = new CoapResource(API); api.add(new CoapTransportResource(coapTransportContext, coapServerService, V1)); diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java index cb403c5e82..989239549c 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java @@ -17,11 +17,13 @@ package org.thingsboard.server.transport.coap; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.eclipse.californium.core.coap.Request; import org.eclipse.californium.core.coap.Response; import org.eclipse.californium.core.network.Exchange; import org.eclipse.californium.core.server.resources.CoapExchange; import org.eclipse.californium.core.server.resources.Resource; +import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageType; @@ -32,16 +34,21 @@ import org.thingsboard.server.gen.transport.TransportProtos; import java.util.List; import java.util.Optional; import java.util.UUID; +import java.util.concurrent.ExecutorService; @Slf4j public class OtaPackageTransportResource extends AbstractCoapTransportResource { private static final int ACCESS_TOKEN_POSITION = 2; private final OtaPackageType otaPackageType; + private final ExecutorService sendOtaDataOutUriLarge; public OtaPackageTransportResource(CoapTransportContext ctx, OtaPackageType otaPackageType) { super(ctx, otaPackageType.getKeyPrefix()); this.otaPackageType = otaPackageType; + + this.setObservable(true); + this.sendOtaDataOutUriLarge = ThingsBoardExecutors.newWorkStealingPool(10, "LwM2M sendOtaDataOutUriLarge"); } @Override @@ -132,11 +139,13 @@ public class OtaPackageTransportResource extends AbstractCoapTransportResource { Response response = new Response(CoAP.ResponseCode.CONTENT); if (data != null && data.length > 0) { response.setPayload(data); + response.getOptions().setAccept(MediaTypeRegistry.APPLICATION_OCTET_STREAM); if (exchange.getRequestOptions().getBlock2() != null) { int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); - boolean lastFlag = data.length > chunkSize; - response.getOptions().setBlock2(chunkSize, lastFlag, 0); - } + boolean lastFlag = data.length <= chunkSize; + this.sendOtaDataOutUriLarge.submit(() -> { + response.getOptions().setBlock2(chunkSize, lastFlag, 0); + }); } exchange.respond(response); } } 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 13d6fd6568..1ec48b31f5 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 @@ -58,10 +58,12 @@ public class LwM2mCredentialsSecurityInfoValidator { public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTransportUtil.LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final TbLwM2MSecurityInfo[] resultSecurityStore = new TbLwM2MSecurityInfo[1]; + log.warn("001) [{}]", credentialsId); context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(credentialsId).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { + log.warn("002) [{}] [{}]", credentialsId, msg); String credentialsBody = msg.getCredentials(); resultSecurityStore[0] = createSecurityInfo(credentialsId, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); @@ -71,6 +73,7 @@ public class LwM2mCredentialsSecurityInfoValidator { @Override public void onError(Throwable e) { + log.warn("003) [{}] [{}] Failed to process credentials ", credentialsId, e); log.trace("[{}] [{}] Failed to process credentials ", credentialsId, e); resultSecurityStore[0] = createSecurityInfo(credentialsId, null, null); latch.countDown(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java index b996922ded..f6456a394c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java @@ -25,8 +25,6 @@ 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.thingsboard.server.cache.ota.OtaPackageDataCache; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -143,7 +141,7 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { response.setPayload(fwData); if (exchange.getRequestOptions().getBlock2() != null) { int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); - boolean lastFlag = fwData.length > chunkSize; + boolean lastFlag = fwData.length <= chunkSize; response.getOptions().setBlock2(chunkSize, lastFlag, 0); log.warn("92) with blokc2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, lastFlag); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index d44770e591..4126541176 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -33,6 +33,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; +import org.thingsboard.server.transport.lwm2m.server.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; @@ -174,6 +175,24 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl fwInfo.setCurrentName(name); } + @Override + public void onCurrentFirmwareStrategyUpdate(LwM2mClient client, Integer newStrategy, String newBaseUrl) { + log.debug("[{}] Current fw strategy: {}", client.getEndpoint(), newStrategy); + LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setFwStrategy(LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(newStrategy)); + fwInfo.setBaseUrl(newBaseUrl); + startFirmwareUpdateIfNeeded(client, fwInfo); + } + + @Override + public void onCurrentSoftwareStrategyUpdate(LwM2mClient client, Integer newStrategy, String newBaseUrl) { + log.debug("[{}] Current sw strategy: {}", client.getEndpoint(), newStrategy); + LwM2MClientOtaInfo swInfo = getOrInitSwInfo(client); + swInfo.setSwStrategy(LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(newStrategy)); + swInfo.setBaseUrl(newBaseUrl); + startSoftwareUpdateIfNeeded(client, swInfo); + } + @Override public void onCurrentFirmwareVersion3Update(LwM2mClient client, String version) { log.debug("[{}] Current fw version: {}", client.getEndpoint(), version); @@ -250,6 +269,10 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } } + private void startSoftwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo swInfo) { + + } + private void startFirmwareUpdateUsingUrl(LwM2mClient client, String url) { String targetIdVer = convertObjectIdToVersionedId(FW_URL_ID, client.getRegistration()); TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(url).timeout(config.getTimeout()).build(); @@ -277,7 +300,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); LwM2MFirmwareUpdateStrategy strategy; if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == 2) { - strategy = fwInfo.getStrategy(); + strategy = fwInfo.getFwStrategy(); } else { strategy = fwInfo.getDeliveryMethod() == 0 ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; } @@ -328,9 +351,9 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl return Optional.empty(); } - private LwM2MClientOtaInfo getOrInitFwInfo(LwM2mClient client) { + public LwM2MClientOtaInfo getOrInitFwInfo(LwM2mClient client) { //TODO: fetch state from the cache or DB. - return fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> { + return this.fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> { var profile = clientContext.getProfile(client.getProfileId()); return new LwM2MClientOtaInfo(endpoint, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy(), profile.getClientLwM2mSettings().getFwUpdateRecourse()); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index 43d4f4acf5..74bf8d1fac 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -19,8 +19,9 @@ import lombok.Data; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; +import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; import java.util.Optional; @@ -44,7 +45,8 @@ public class LwM2MClientOtaInfo { private Integer deliveryMethod; //TODO: use value from device if applicable; - private LwM2MFirmwareUpdateStrategy strategy; + private LwM2MFirmwareUpdateStrategy fwStrategy; + private LwM2MSoftwareUpdateStrategy swStrategy; private UpdateStateFw updateState; private UpdateResultFw updateResult; @@ -54,7 +56,7 @@ public class LwM2MClientOtaInfo { public LwM2MClientOtaInfo(String endpoint, OtaPackageType type, Integer strategyCode, String baseUrl) { this.endpoint = endpoint; this.type = type; - this.strategy = LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(strategyCode); + this.fwStrategy = LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(strategyCode); this.baseUrl = baseUrl; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java index 9c7905a5e8..644099ee9d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java @@ -31,6 +31,10 @@ public interface LwM2MOtaUpdateService { void onCurrentFirmwareNameUpdate(LwM2mClient client, String name); + void onCurrentFirmwareStrategyUpdate(LwM2mClient client, Integer newStrategy, String newBaseUrl); + + void onCurrentSoftwareStrategyUpdate(LwM2mClient client, Integer newStrategy, String newBaseUrl); + void onCurrentFirmwareVersion3Update(LwM2mClient client, String version); void onCurrentFirmwareVersion5Update(LwM2mClient client, String version); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 5b61ffc768..0810d1a423 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -37,7 +37,6 @@ import org.eclipse.leshan.server.registration.Registration; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.DonAsynchron; -import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; @@ -53,6 +52,8 @@ 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.LwM2MFirmwareUpdateStrategy; +import org.thingsboard.server.transport.lwm2m.server.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.LwM2mOtaConvert; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; @@ -100,7 +101,6 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -779,6 +779,29 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl clients.forEach(client -> sendCancelObserveRequest(targetId, client)); } } + + // # 7.1 + // update value in fwInfo + if (!newProfile.getClientLwM2mSettings().getFwUpdateStrategy().equals(oldProfile.getClientLwM2mSettings().getFwUpdateStrategy()) + || (LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == newProfile.getClientLwM2mSettings().getFwUpdateStrategy() && + !newProfile.getClientLwM2mSettings().getFwUpdateRecourse().equals(oldProfile.getClientLwM2mSettings().getFwUpdateRecourse()))) { + clients.forEach(lwM2MClient -> { + otaService.onCurrentFirmwareStrategyUpdate(lwM2MClient, + newProfile.getClientLwM2mSettings().getFwUpdateStrategy(), + newProfile.getClientLwM2mSettings().getFwUpdateRecourse()); + }); + } + + //# 7.2 // update value in swInfo + if (!newProfile.getClientLwM2mSettings().getSwUpdateStrategy().equals(oldProfile.getClientLwM2mSettings().getSwUpdateStrategy()) + || (LwM2MSoftwareUpdateStrategy.TEMP_URL.code == newProfile.getClientLwM2mSettings().getSwUpdateStrategy() && + !newProfile.getClientLwM2mSettings().getSwUpdateRecourse().equals(oldProfile.getClientLwM2mSettings().getSwUpdateRecourse()))) { + clients.forEach(lwM2MClient -> { + otaService.onCurrentSoftwareStrategyUpdate(lwM2MClient, + newProfile.getClientLwM2mSettings().getFwUpdateStrategy(), + newProfile.getClientLwM2mSettings().getFwUpdateRecourse()); + }); + } } } 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 30b78393d3..5286161651 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 @@ -160,9 +160,9 @@ - + - + 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 faf94eccce..f99f1629ab 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 @@ -97,7 +97,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro binding: [], bootstrapServer: [null, Validators.required], lwm2mServer: [null, Validators.required], - clientStrategy: [1, []], + clientOnlyObserveAfterConnect: [1, []], fwUpdateStrategy: [1, []], swUpdateStrategy: [1, []], fwUpdateRecourse: [{value: '', disabled: true}, []], @@ -216,7 +216,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro binding: this.configurationValue.bootstrap.servers.binding, bootstrapServer: this.configurationValue.bootstrap.bootstrapServer, lwm2mServer: this.configurationValue.bootstrap.lwm2mServer, - clientStrategy: this.configurationValue.clientLwM2mSettings.clientStrategy, + clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1, fwUpdateRecourse: fwResource, @@ -257,7 +257,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro bootstrapServers.defaultMinPeriod = config.defaultMinPeriod; bootstrapServers.notifIfDisabled = config.notifIfDisabled; bootstrapServers.binding = config.binding; - this.configurationValue.clientLwM2mSettings.clientStrategy = config.clientStrategy; + this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect = config.clientOnlyObserveAfterConnect; this.configurationValue.clientLwM2mSettings.fwUpdateStrategy = config.fwUpdateStrategy; this.configurationValue.clientLwM2mSettings.swUpdateStrategy = config.swUpdateStrategy; this.configurationValue.clientLwM2mSettings.fwUpdateRecourse = config.fwUpdateRecourse; 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 1f2d125d9f..594147e5af 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 @@ -168,7 +168,7 @@ export interface Lwm2mProfileConfigModels { } export interface ClientLwM2mSettings { - clientStrategy: string; + clientOnlyObserveAfterConnect: number; fwUpdateStrategy: number; swUpdateStrategy: number; fwUpdateRecourse: string; @@ -240,7 +240,7 @@ export function getDefaultProfileConfig(hostname?: any): Lwm2mProfileConfigModel function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { return { - clientStrategy: '1', + clientOnlyObserveAfterConnect: 1, fwUpdateStrategy: 1, swUpdateStrategy: 1, fwUpdateRecourse: DEFAULT_FW_UPDATE_RESOURCE, From a34ffe8a409e353cf73faeaf83e6cc22c518a0ed Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Sun, 20 Jun 2021 14:09:20 +0300 Subject: [PATCH 451/476] LWM2M: merge with master --- .../secure/LwM2MBootstrapSecurityStore.java | 8 +- ...LwM2mCredentialsSecurityInfoValidator.java | 9 +- .../TbLwM2MDtlsCertificateVerifier.java | 5 +- .../server/DefaultLwM2mTransportService.java | 2 +- .../server/LwM2mTransportCoapResource.java | 4 +- .../lwm2m/server/LwM2mTransportUtil.java | 352 ++---------------- .../lwm2m/server/client/LwM2mClient.java | 6 +- .../ota/DefaultLwM2MOtaUpdateService.java | 123 +++++- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 8 +- .../LwM2MFirmwareUpdateStrategy.java | 2 +- .../server/ota/firmware/UpdateDeliveryFw.java | 55 +++ .../{ => ota/firmware}/UpdateResultFw.java | 2 +- .../{ => ota/firmware}/UpdateStateFw.java | 2 +- .../LwM2MSoftwareUpdateStrategy.java | 2 +- .../server/ota/software/UpdateResultSw.java | 84 +++++ .../server/ota/software/UpdateStateSw.java | 62 +++ .../server/store/TbLwM2mSecurityStore.java | 11 +- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 16 +- .../lwm2m/server/uplink/LwM2mTypeServer.java | 39 ++ 19 files changed, 408 insertions(+), 384 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{ => ota/firmware}/LwM2MFirmwareUpdateStrategy.java (96%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateDeliveryFw.java rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{ => ota/firmware}/UpdateResultFw.java (97%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{ => ota/firmware}/UpdateStateFw.java (96%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{ => ota/software}/LwM2MSoftwareUpdateStrategy.java (95%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateResultSw.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateStateSw.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mTypeServer.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 80aa42d6af..7740778adf 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 @@ -29,12 +29,11 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; 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.LwM2mTransportUtil; import java.io.IOException; import java.security.GeneralSecurityException; @@ -46,6 +45,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getBootstrapParametersFromThingsboard; +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP; @Slf4j @Service("LwM2MBootstrapSecurityStore") @@ -68,7 +68,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public Iterator getAllByEndpoint(String endPoint) { - TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP); if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* add value to store from BootstrapJson */ this.setBootstrapConfigScurityInfo(store); @@ -92,7 +92,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { @Override public SecurityInfo getByIdentity(String identity) { - TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP); + TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, BOOTSTRAP); if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { /* 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 1ec48b31f5..8bd97a952e 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 @@ -33,7 +33,7 @@ import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer; import java.io.IOException; import java.security.GeneralSecurityException; @@ -45,6 +45,7 @@ import static org.eclipse.leshan.core.SecurityMode.NO_SEC; import static org.eclipse.leshan.core.SecurityMode.PSK; import static org.eclipse.leshan.core.SecurityMode.RPK; import static org.eclipse.leshan.core.SecurityMode.X509; +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP; @Slf4j @Component @@ -55,7 +56,7 @@ public class LwM2mCredentialsSecurityInfoValidator { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTransportUtil.LwM2mTypeServer keyValue) { + public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final TbLwM2MSecurityInfo[] resultSecurityStore = new TbLwM2MSecurityInfo[1]; log.warn("001) [{}]", credentialsId); @@ -94,11 +95,11 @@ public class LwM2mCredentialsSecurityInfoValidator { * @param keyValue - * @return SecurityInfo */ - private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTransportUtil.LwM2mTypeServer keyValue) { + private TbLwM2MSecurityInfo createSecurityInfo(String endpoint, String jsonStr, LwM2mTypeServer keyValue) { TbLwM2MSecurityInfo result = new TbLwM2MSecurityInfo(); LwM2MCredentials credentials = JacksonUtil.fromString(jsonStr, LwM2MCredentials.class); if (credentials != null) { - if (keyValue.equals(LwM2mTransportUtil.LwM2mTypeServer.BOOTSTRAP)) { + if (keyValue.equals(BOOTSTRAP)) { result.setBootstrapCredentialConfig(credentials.getBootstrap()); if (LwM2MSecurityMode.PSK.equals(credentials.getClient().getSecurityConfigClientMode())) { PSKClientCredentials pskClientConfig = (PSKClientCredentials) credentials.getClient(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index dcd7fa89bb..792ba131e8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -42,7 +42,6 @@ import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; @@ -57,6 +56,8 @@ import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.List; +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT; + @Slf4j @Component @TbLwM2mTransportComponent @@ -117,7 +118,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer String strCert = SslUtil.getCertificateString(cert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert); - TbLwM2MSecurityInfo securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); + TbLwM2MSecurityInfo securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT); ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null; if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { LwM2MCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MCredentials.class); 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 9425ef7891..c027267197 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 @@ -65,7 +65,7 @@ import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE 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.LwM2mNetworkConfig.getCoapConfig; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RECOURSE; @Slf4j @Component diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java index f6456a394c..105943eab8 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportCoapResource.java @@ -31,8 +31,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SOFTWARE_UPDATE_COAP_RECOURSE; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FIRMWARE_UPDATE_COAP_RECOURSE; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SOFTWARE_UPDATE_COAP_RECOURSE; @Slf4j public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { 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 d04e4993f1..64f80f7353 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 @@ -43,13 +43,13 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.ota.OtaPackageKey; -import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; -import org.thingsboard.server.common.data.ota.OtaPackageUtil; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateResultFw; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.ota.software.UpdateResultSw; +import org.thingsboard.server.transport.lwm2m.server.ota.software.UpdateStateSw; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import java.util.ArrayList; @@ -77,277 +77,20 @@ 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; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_RESULT_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_UPDATE_STATE_ID; @Slf4j public class LwM2mTransportUtil { - public static final String EVENT_AWAKE = "AWAKE"; - public static final String RESPONSE_REQUEST_CHANNEL = "RESP_REQ"; - public static final String RESPONSE_CHANNEL = "RESP"; - public static final String OBSERVE_CHANNEL = "OBSERVE"; - - public static final String TRANSPORT_DEFAULT_LWM2M_VERSION = "1.0"; - public static final String CLIENT_LWM2M_SETTINGS = "clientLwM2mSettings"; - public static final String BOOTSTRAP = "bootstrap"; - public static final String SERVERS = "servers"; - public static final String LWM2M_SERVER = "lwm2mServer"; - public static final String BOOTSTRAP_SERVER = "bootstrapServer"; - public static final String OBSERVE_ATTRIBUTE_TELEMETRY = "observeAttr"; - public static final String ATTRIBUTE = "attribute"; - public static final String TELEMETRY = "telemetry"; - public static final String KEY_NAME = "keyName"; - public static final String OBSERVE_LWM2M = "observe"; - public static final String ATTRIBUTE_LWM2M = "attributeLwm2m"; - - private static final String REQUEST = "/request"; - private static final String ATTRIBUTES = "/" + ATTRIBUTE; - public static final String TELEMETRIES = "/" + TELEMETRY; - public static final String ATTRIBUTES_REQUEST = ATTRIBUTES + REQUEST; - public static final String DEVICE_ATTRIBUTES_REQUEST = ATTRIBUTES_REQUEST + "/"; - - public static final long DEFAULT_TIMEOUT = 2 * 60 * 1000L; // 2min in ms + public static final String LWM2M_VERSION_DEFAULT = "1.0"; public static final String LOG_LWM2M_TELEMETRY = "logLwm2m"; public static final String LOG_LWM2M_INFO = "info"; public static final String LOG_LWM2M_ERROR = "error"; public static final String LOG_LWM2M_WARN = "warn"; - public static final String LOG_LWM2M_VALUE = "value"; - - public static final String CLIENT_NOT_AUTHORIZED = "Client not authorized"; - public static final String LWM2M_VERSION_DEFAULT = "1.0"; - - // Firmware - public static final String FIRMWARE_UPDATE_COAP_RECOURSE = "tbfw"; - public static final String FW_UPDATE = "Firmware update"; - public static final Integer FW_5_ID = 5; - public static final Integer FW_19_ID = 19; - - // Package W - public static final String FW_PACKAGE_5_ID = "/5/0/0"; - public static final String FW_PACKAGE_19_ID = "/19/0/0"; - // Package URI - public static final String FW_PACKAGE_URI_ID = "/5/0/1"; - // State R - public static final String FW_STATE_ID = "/5/0/3"; - // Update Result R - public static final String FW_RESULT_ID = "/5/0/5"; - - public static final String FW_DELIVERY_METHOD = "/5/0/9"; - - // PkgName R - public static final String FW_NAME_ID = "/5/0/6"; - // PkgVersion R - public static final String FW_5_VER_ID = "/5/0/7"; - - /** - * Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8 - * BC68JAR01A10 - * # Request prodct type number - * ATI - * Quectel - * BC68 - * Revision:BC68JAR01A10 - */ - public static final String FW_3_VER_ID = "/3/0/3"; - // Update E - public static final String FW_UPDATE_ID = "/5/0/2"; - - // Software - public static final String SOFTWARE_UPDATE_COAP_RECOURSE = "softwareUpdateCoapRecourse"; - public static final String SW_UPDATE = "Software update"; - public static final Integer SW_ID = 9; - // Package W - public static final String SW_PACKAGE_ID = "/9/0/2"; - // Package URI - public static final String SW_PACKAGE_URI_ID = "/9/0/3"; - // Update State R - public static final String SW_UPDATE_STATE_ID = "/9/0/7"; - // Update Result R - public static final String SW_RESULT_ID = "/9/0/9"; - // PkgName R - public static final String SW_NAME_ID = "/9/0/0"; - // PkgVersion R - public static final String SW_VER_ID = "/9/0/1"; - // Install E - public static final String SW_INSTALL_ID = "/9/0/4"; - // Uninstall E - public static final String SW_UN_INSTALL_ID = "/9/0/6"; - - public enum LwM2mTypeServer { - BOOTSTRAP(0, "bootstrap"), - CLIENT(1, "client"); - - public int code; - public String type; - - LwM2mTypeServer(int code, String type) { - this.code = code; - this.type = type; - } - - public static LwM2mTypeServer fromLwM2mTypeServer(String type) { - for (LwM2mTypeServer sm : LwM2mTypeServer.values()) { - if (sm.type.equals(type)) { - return sm; - } - } - throw new IllegalArgumentException(String.format("Unsupported typeServer type : %d", type)); - } - } - - public static Optional toOtaPackageUpdateStatus(UpdateResultFw updateResultFw) { - switch (updateResultFw) { - case INITIAL: - return Optional.empty(); - case UPDATE_SUCCESSFULLY: - return Optional.of(UPDATED); - case NOT_ENOUGH: - case OUT_OFF_MEMORY: - case CONNECTION_LOST: - case INTEGRITY_CHECK_FAILURE: - case UNSUPPORTED_TYPE: - case INVALID_URI: - case UPDATE_FAILED: - case UNSUPPORTED_PROTOCOL: - return Optional.of(FAILED); - default: - throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", updateResultFw.name()); - } - } - - public static Optional toOtaPackageUpdateStatus(UpdateStateFw updateStateFw) { - switch (updateStateFw) { - case IDLE: - return Optional.empty(); - case DOWNLOADING: - return Optional.of(DOWNLOADING); - case DOWNLOADED: - return Optional.of(DOWNLOADED); - case UPDATING: - return Optional.of(UPDATING); - default: - throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", updateStateFw); - } - } - - /** - * SW Update State R - * 0: INITIAL Before downloading. (see 5.1.2.1) - * 1: DOWNLOAD STARTED The downloading process has started and is on-going. (see 5.1.2.2) - * 2: DOWNLOADED The package has been completely downloaded (see 5.1.2.3) - * 3: DELIVERED In that state, the package has been correctly downloaded and is ready to be installed. (see 5.1.2.4) - * If executing the Install Resource failed, the state remains at DELIVERED. - * If executing the Install Resource was successful, the state changes from DELIVERED to INSTALLED. - * After executing the UnInstall Resource, the state changes to INITIAL. - * 4: INSTALLED - */ - public enum UpdateStateSw { - INITIAL(0, "Initial"), - DOWNLOAD_STARTED(1, "DownloadStarted"), - DOWNLOADED(2, "Downloaded"), - DELIVERED(3, "Delivered"), - INSTALLED(4, "Installed"); - - public int code; - public String type; - - UpdateStateSw(int code, String type) { - this.code = code; - this.type = type; - } - - public static UpdateStateSw fromUpdateStateSwByType(String type) { - for (UpdateStateSw to : UpdateStateSw.values()) { - if (to.type.equals(type)) { - return to; - } - } - throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type)); - } - - public static UpdateStateSw fromUpdateStateSwByCode(int code) { - for (UpdateStateSw to : UpdateStateSw.values()) { - if (to.code == code) { - return to; - } - } - throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", code)); - } - } - - /** - * SW Update Result - * Contains the result of downloading or installing/uninstalling the software - * 0: Initial value. - * - Prior to download any new package in the Device, Update Result MUST be reset to this initial value. - * - One side effect of executing the Uninstall resource is to reset Update Result to this initial value "0". - * 1: Downloading. - * - The package downloading process is on-going. - * 2: Software successfully installed. - * 3: Successfully Downloaded and package integrity verified - * (( 4-49, for expansion, of other scenarios)) - * ** Failed - * 50: Not enough storage for the new software package. - * 51: Out of memory during downloading process. - * 52: Connection lost during downloading process. - * 53: Package integrity check failure. - * 54: Unsupported package type. - * 56: Invalid URI - * 57: Device defined update error - * 58: Software installation failure - * 59: Uninstallation Failure during forUpdate(arg=0) - * 60-200 : (for expansion, selection to be in blocks depending on new introduction of features) - * This Resource MAY be reported by sending Observe operation. - */ - public enum UpdateResultSw { - INITIAL(0, "Initial value", false), - DOWNLOADING(1, "Downloading", false), - SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false), - SUCCESSFULLY_DOWNLOADED_VERIFIED(3, "Successfully Downloaded and package integrity verified", false), - NOT_ENOUGH_STORAGE(50, "Not enough storage for the new software package", true), - OUT_OFF_MEMORY(51, "Out of memory during downloading process", true), - CONNECTION_LOST(52, "Connection lost during downloading process", false), - PACKAGE_CHECK_FAILURE(53, "Package integrity check failure.", false), - UNSUPPORTED_PACKAGE_TYPE(54, "Unsupported package type", false), - INVALID_URI(56, "Invalid URI", true), - UPDATE_ERROR(57, "Device defined update error", true), - INSTALL_FAILURE(58, "Software installation failure", true), - UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true); - - public int code; - public String type; - public boolean isAgain; - - UpdateResultSw(int code, String type, boolean isAgain) { - this.code = code; - this.type = type; - this.isAgain = isAgain; - } - - public static UpdateResultSw fromUpdateResultSwByType(String type) { - for (UpdateResultSw to : UpdateResultSw.values()) { - if (to.type.equals(type)) { - return to; - } - } - throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type)); - } - - public static UpdateResultSw fromUpdateResultSwByCode(int code) { - for (UpdateResultSw to : UpdateResultSw.values()) { - if (to.code == code) { - return to; - } - } - throw new IllegalArgumentException(String.format("Unsupported SW Update Result code : %s", code)); - } - } public enum LwM2MClientStrategy { CLIENT_STRATEGY_1(1, "Read only resources marked as observation"), @@ -380,45 +123,7 @@ public class LwM2mTransportUtil { } } - /** - * FirmwareUpdateStatus { - * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED - */ - public static OtaPackageUpdateStatus EqualsSwSateToFirmwareUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) { - switch (updateResultSw) { - case INITIAL: - switch (updateStateSw) { - case INITIAL: - case DOWNLOAD_STARTED: - return DOWNLOADING; - case DOWNLOADED: - return DOWNLOADED; - case DELIVERED: - return VERIFIED; - } - case DOWNLOADING: - return DOWNLOADING; - case SUCCESSFULLY_INSTALLED: - return UPDATED; - case SUCCESSFULLY_DOWNLOADED_VERIFIED: - return VERIFIED; - case NOT_ENOUGH_STORAGE: - case OUT_OFF_MEMORY: - case CONNECTION_LOST: - case PACKAGE_CHECK_FAILURE: - case UNSUPPORTED_PACKAGE_TYPE: - case INVALID_URI: - case UPDATE_ERROR: - case INSTALL_FAILURE: - case UN_INSTALL_FAILURE: - return FAILED; - default: - throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", updateStateSw.name(), updateResultSw.name()); - } - } - - - public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath + public static boolean equalsResourceValue(Object valueOld, Object valueNew, ResourceModel.Type type, LwM2mPath resourcePath) throws CodecException { switch (type) { case BOOLEAN: @@ -477,18 +182,6 @@ public class LwM2mTransportUtil { return null; } -// public static LwM2mClientProfile getNewProfileParameters(JsonObject profilesConfigData, TenantId tenantId) { -// LwM2mClientProfile lwM2MClientProfile = new LwM2mClientProfile(); -// lwM2MClientProfile.setTenantId(tenantId); -// lwM2MClientProfile.setPostClientLwM2mSettings(profilesConfigData.get(CLIENT_LWM2M_SETTINGS).getAsJsonObject()); -// lwM2MClientProfile.setPostKeyNameProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).getAsJsonObject()); -// lwM2MClientProfile.setPostAttributeProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).getAsJsonArray()); -// lwM2MClientProfile.setPostTelemetryProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(TELEMETRY).getAsJsonArray()); -// lwM2MClientProfile.setPostObserveProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(OBSERVE_LWM2M).getAsJsonArray()); -// lwM2MClientProfile.setPostAttributeLwm2mProfile(profilesConfigData.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE_LWM2M).getAsJsonObject()); -// return lwM2MClientProfile; -// } - public static Lwm2mDeviceProfileTransportConfiguration toLwM2MClientProfile(DeviceProfile deviceProfile) { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); if (transportConfiguration.getType().equals(DeviceTransportType.LWM2M)) { @@ -603,7 +296,6 @@ public class LwM2mTransportUtil { if (keyArray.length > 1 && keyArray[1].split(LWM2M_SEPARATOR_KEY).length == 2) { return pathIdVer; } else { - LwM2mPath pathObjId = new LwM2mPath(pathIdVer); return convertObjectIdToVersionedId(pathIdVer, registration); } } @@ -743,18 +435,18 @@ public class LwM2mTransportUtil { } } - public static boolean isFwSwWords(String pathName) { - return OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.SIZE).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName) - || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.SIZE).equals(pathName); - } +// public static boolean isFwSwWords(String pathName) { +// return OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.SIZE).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName) +// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.SIZE).equals(pathName); +// } /** * @param lwM2MClient - 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 0e71a1e865..dbcb82a167 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 @@ -33,12 +33,10 @@ 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.common.data.id.TenantId; -import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; -import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Collection; import java.util.Map; @@ -53,7 +51,7 @@ import java.util.concurrent.locks.ReentrantLock; 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.LwM2mTransportUtil.TRANSPORT_DEFAULT_LWM2M_VERSION; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_VERSION_DEFAULT; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.equalsResourceTypeGetSimpleName; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; @@ -307,7 +305,7 @@ public class LwM2mClient implements Cloneable { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(path)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verRez = getVerFromPathIdVerOrId(path); - return verRez == null ? TRANSPORT_DEFAULT_LWM2M_VERSION.equals(verSupportedObject) : verRez.equals(verSupportedObject); + return verRez == null ? LWM2M_VERSION_DEFAULT.equals(verSupportedObject) : verRez.equals(verSupportedObject); } /** diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 4126541176..8f0a9da33f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.ota; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.request.ContentFormat; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -32,12 +33,7 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; -import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; -import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; import org.thingsboard.server.transport.lwm2m.server.attributes.LwM2MAttributesService; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; @@ -48,6 +44,13 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateDeliveryFw; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateResultFw; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; +import org.thingsboard.server.transport.lwm2m.server.ota.software.UpdateResultSw; +import org.thingsboard.server.transport.lwm2m.server.ota.software.UpdateStateSw; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import javax.annotation.PostConstruct; @@ -60,8 +63,13 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import static org.thingsboard.server.common.data.ota.OtaPackageKey.STATE; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.DOWNLOADING; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.UPDATING; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.VERIFIED; import static org.thingsboard.server.common.data.ota.OtaPackageUtil.getAttributeKey; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FIRMWARE_UPDATE_COAP_RECOURSE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; @@ -78,13 +86,30 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl public static final String SOFTWARE_TITLE = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE); public static final String SOFTWARE_URL = getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.URL); + public static final String FIRMWARE_UPDATE_COAP_RECOURSE = "tbfw"; + public static final String SOFTWARE_UPDATE_COAP_RECOURSE = "tbsw"; private static final String FW_PACKAGE_5_ID = "/5/0/0"; private static final String FW_URL_ID = "/5/0/1"; private static final String FW_EXECUTE_ID = "/5/0/2"; - private static final String FW_NAME_ID = "/5/0/6"; - private static final String FW_VER_ID = "/5/0/7"; + public static final String FW_STATE_ID = "/5/0/3"; + public static final String FW_RESULT_ID = "/5/0/5"; + public static final String FW_NAME_ID = "/5/0/6"; + public static final String FW_5_VER_ID = "/5/0/7"; + /** + * Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8 + * Revision:BC68JAR01A10 + */ + public static final String FW_3_VER_ID = "/3/0/3"; + public static final String FW_DELIVERY_METHOD = "/5/0/9"; + private static final String SW_NAME_ID = "/9/0/0"; private static final String SW_VER_ID = "/9/0/1"; + public static final String SW_PACKAGE_ID = "/9/0/2"; + public static final String SW_PACKAGE_URI_ID = "/9/0/3"; + public static final String SW_INSTALL_ID = "/9/0/4"; + public static final String SW_UPDATE_STATE_ID = "/9/0/7"; + public static final String SW_RESULT_ID = "/9/0/9"; + public static final String SW_UN_INSTALL_ID = "/9/0/6"; private final Map fwStates = new ConcurrentHashMap<>(); private final Map swStates = new ConcurrentHashMap<>(); @@ -216,7 +241,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl executeFwUpdate(client); } fwInfo.setUpdateState(state); - Optional status = LwM2mTransportUtil.toOtaPackageUpdateStatus(state); + Optional status = this.toOtaPackageUpdateStatus(state); status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, otaStatus, "Firmware Update State: " + state.name())); } @@ -226,7 +251,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl log.debug("[{}] Current fw result: {}", client.getEndpoint(), code); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); UpdateResultFw result = UpdateResultFw.fromUpdateResultFwByCode(code.intValue()); - Optional status = LwM2mTransportUtil.toOtaPackageUpdateStatus(result); + Optional status = this.toOtaPackageUpdateStatus(result); status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, otaStatus, "Firmware Update Result: " + result.name())); if (result.isAgain() && fwInfo.getRetryAttempts() <= 2) { @@ -299,10 +324,10 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); LwM2MFirmwareUpdateStrategy strategy; - if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == 2) { + if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == UpdateDeliveryFw.BOTH.code) { strategy = fwInfo.getFwStrategy(); } else { - strategy = fwInfo.getDeliveryMethod() == 0 ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; + strategy = fwInfo.getDeliveryMethod() == UpdateDeliveryFw.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; } switch (strategy) { case OBJ_5_BINARY: @@ -380,4 +405,78 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl helper.sendParametersOnThingsboardTelemetry(result, client.getSession()); } + private static Optional toOtaPackageUpdateStatus(UpdateResultFw updateResultFw) { + switch (updateResultFw) { + case INITIAL: + return Optional.empty(); + case UPDATE_SUCCESSFULLY: + return Optional.of(UPDATED); + case NOT_ENOUGH: + case OUT_OFF_MEMORY: + case CONNECTION_LOST: + case INTEGRITY_CHECK_FAILURE: + case UNSUPPORTED_TYPE: + case INVALID_URI: + case UPDATE_FAILED: + case UNSUPPORTED_PROTOCOL: + return Optional.of(FAILED); + default: + throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", updateResultFw.name()); + } + } + + private static Optional toOtaPackageUpdateStatus(UpdateStateFw updateStateFw) { + switch (updateStateFw) { + case IDLE: + return Optional.empty(); + case DOWNLOADING: + return Optional.of(DOWNLOADING); + case DOWNLOADED: + return Optional.of(DOWNLOADED); + case UPDATING: + return Optional.of(UPDATING); + default: + throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", updateStateFw); + } + } + + /** + * FirmwareUpdateStatus { + * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED + */ + public static Optional toSwSateResultUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) { + switch (updateResultSw) { + case INITIAL: + switch (updateStateSw) { + case INITIAL: + case DOWNLOAD_STARTED: + return Optional.of(DOWNLOADING); + case DOWNLOADED: + return Optional.of(DOWNLOADED); + case DELIVERED: + return Optional.of(VERIFIED); + } + case DOWNLOADING: + return Optional.of(DOWNLOADING); + case SUCCESSFULLY_INSTALLED: + return Optional.of(UPDATED); + case SUCCESSFULLY_DOWNLOADED_VERIFIED: + return Optional.of(VERIFIED); + case NOT_ENOUGH_STORAGE: + case OUT_OFF_MEMORY: + case CONNECTION_LOST: + case PACKAGE_CHECK_FAILURE: + case UNSUPPORTED_PACKAGE_TYPE: + case INVALID_URI: + case UPDATE_ERROR: + case INSTALL_FAILURE: + case UN_INSTALL_FAILURE: + return Optional.of(FAILED); + default: + throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", updateStateSw.name(), updateResultSw.name()); + } + } + + + } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index 74bf8d1fac..98e2055fdc 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -18,10 +18,10 @@ package org.thingsboard.server.transport.lwm2m.server.ota; import lombok.Data; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.transport.lwm2m.server.LwM2MFirmwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.LwM2MSoftwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.UpdateResultFw; -import org.thingsboard.server.transport.lwm2m.server.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateResultFw; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; import java.util.Optional; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MFirmwareUpdateStrategy.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MFirmwareUpdateStrategy.java similarity index 96% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MFirmwareUpdateStrategy.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MFirmwareUpdateStrategy.java index f111152b76..d8ce5b1fbb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MFirmwareUpdateStrategy.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MFirmwareUpdateStrategy.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.server; +package org.thingsboard.server.transport.lwm2m.server.ota.firmware; public enum LwM2MFirmwareUpdateStrategy { OBJ_5_BINARY(1, "ObjectId 5, Binary"), diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateDeliveryFw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateDeliveryFw.java new file mode 100644 index 0000000000..dcb93a5283 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateDeliveryFw.java @@ -0,0 +1,55 @@ +/** + * 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.ota.firmware; + +/** + * /** Delivery Method R + * 0: Pull only + * 1: Push only + * 2: Both: + * - In this case the LwM2M Server MAY choose the preferred mechanism for conveying the firmware image to the LwM2M Client. + */ +public enum UpdateDeliveryFw { + PULL(0, "Pull only"), + PUSH(1, "Push only"), + BOTH(2, "Push or Push"); + + public int code; + public String type; + + UpdateDeliveryFw(int code, String type) { + this.code = code; + this.type = type; + } + + public static UpdateDeliveryFw fromStateFwByType(String type) { + for (UpdateDeliveryFw to : UpdateDeliveryFw.values()) { + if (to.type.equals(type)) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported FW delivery type : %s", type)); + } + + public static UpdateDeliveryFw fromStateFwByCode(int code) { + for (UpdateDeliveryFw to : UpdateDeliveryFw.values()) { + if (to.code == code) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported FW delivery code : %s", code)); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateResultFw.java similarity index 97% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateResultFw.java index 79361aeb1d..5a71f41bc5 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateResultFw.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateResultFw.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.server; +package org.thingsboard.server.transport.lwm2m.server.ota.firmware; import lombok.Getter; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateStateFw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateStateFw.java similarity index 96% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateStateFw.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateStateFw.java index b47a96e9bd..447749fd50 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/UpdateStateFw.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateStateFw.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.server; +package org.thingsboard.server.transport.lwm2m.server.ota.firmware; /** * /** State R diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSoftwareUpdateStrategy.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MSoftwareUpdateStrategy.java similarity index 95% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSoftwareUpdateStrategy.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MSoftwareUpdateStrategy.java index 0df3f5ca2d..5fbdc25a2f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2MSoftwareUpdateStrategy.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MSoftwareUpdateStrategy.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.lwm2m.server; +package org.thingsboard.server.transport.lwm2m.server.ota.software; public enum LwM2MSoftwareUpdateStrategy { BINARY(1, "ObjectId 9, Binary"), diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateResultSw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateResultSw.java new file mode 100644 index 0000000000..22c92205d0 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateResultSw.java @@ -0,0 +1,84 @@ +/** + * 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.ota.software; + +/** + * SW Update Result + * Contains the result of downloading or installing/uninstalling the software + * 0: Initial value. + * - Prior to download any new package in the Device, Update Result MUST be reset to this initial value. + * - One side effect of executing the Uninstall resource is to reset Update Result to this initial value "0". + * 1: Downloading. + * - The package downloading process is on-going. + * 2: Software successfully installed. + * 3: Successfully Downloaded and package integrity verified + * (( 4-49, for expansion, of other scenarios)) + * ** Failed + * 50: Not enough storage for the new software package. + * 51: Out of memory during downloading process. + * 52: Connection lost during downloading process. + * 53: Package integrity check failure. + * 54: Unsupported package type. + * 56: Invalid URI + * 57: Device defined update error + * 58: Software installation failure + * 59: Uninstallation Failure during forUpdate(arg=0) + * 60-200 : (for expansion, selection to be in blocks depending on new introduction of features) + * This Resource MAY be reported by sending Observe operation. + */ +public enum UpdateResultSw { + INITIAL(0, "Initial value", false), + DOWNLOADING(1, "Downloading", false), + SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false), + SUCCESSFULLY_DOWNLOADED_VERIFIED(3, "Successfully Downloaded and package integrity verified", false), + NOT_ENOUGH_STORAGE(50, "Not enough storage for the new software package", true), + OUT_OFF_MEMORY(51, "Out of memory during downloading process", true), + CONNECTION_LOST(52, "Connection lost during downloading process", false), + PACKAGE_CHECK_FAILURE(53, "Package integrity check failure.", false), + UNSUPPORTED_PACKAGE_TYPE(54, "Unsupported package type", false), + INVALID_URI(56, "Invalid URI", true), + UPDATE_ERROR(57, "Device defined update error", true), + INSTALL_FAILURE(58, "Software installation failure", true), + UN_INSTALL_FAILURE(59, "Uninstallation Failure during forUpdate(arg=0)", true); + + public int code; + public String type; + public boolean isAgain; + + UpdateResultSw(int code, String type, boolean isAgain) { + this.code = code; + this.type = type; + this.isAgain = isAgain; + } + + public static UpdateResultSw fromUpdateResultSwByType(String type) { + for (UpdateResultSw to : UpdateResultSw.values()) { + if (to.type.equals(type)) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type)); + } + + public static UpdateResultSw fromUpdateResultSwByCode(int code) { + for (UpdateResultSw to : UpdateResultSw.values()) { + if (to.code == code) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported SW Update Result code : %s", code)); + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateStateSw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateStateSw.java new file mode 100644 index 0000000000..a5f772a11d --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateStateSw.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.transport.lwm2m.server.ota.software; + +/** + * SW Update State R + * 0: INITIAL Before downloading. (see 5.1.2.1) + * 1: DOWNLOAD STARTED The downloading process has started and is on-going. (see 5.1.2.2) + * 2: DOWNLOADED The package has been completely downloaded (see 5.1.2.3) + * 3: DELIVERED In that state, the package has been correctly downloaded and is ready to be installed. (see 5.1.2.4) + * If executing the Install Resource failed, the state remains at DELIVERED. + * If executing the Install Resource was successful, the state changes from DELIVERED to INSTALLED. + * After executing the UnInstall Resource, the state changes to INITIAL. + * 4: INSTALLED + */ +public enum UpdateStateSw { + INITIAL(0, "Initial"), + DOWNLOAD_STARTED(1, "DownloadStarted"), + DOWNLOADED(2, "Downloaded"), + DELIVERED(3, "Delivered"), + INSTALLED(4, "Installed"); + + public int code; + public String type; + + UpdateStateSw(int code, String type) { + this.code = code; + this.type = type; + } + + public static UpdateStateSw fromUpdateStateSwByType(String type) { + for (UpdateStateSw to : UpdateStateSw.values()) { + if (to.type.equals(type)) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type)); + } + + public static UpdateStateSw fromUpdateStateSwByCode(int code) { + for (UpdateStateSw to : UpdateStateSw.values()) { + if (to.code == code) { + return to; + } + } + throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", code)); + } +} + 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 index 293e68ec39..d47be49978 100644 --- 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 @@ -16,20 +16,13 @@ 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.SecurityStore; -import org.eclipse.leshan.server.security.SecurityStoreListener; import org.jetbrains.annotations.Nullable; -import org.springframework.stereotype.Component; -import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext; -import java.util.Collection; +import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT; @Slf4j public class TbLwM2mSecurityStore implements TbEditableSecurityStore { @@ -67,7 +60,7 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore { @Nullable public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) { - TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, LwM2mTransportUtil.LwM2mTypeServer.CLIENT); + TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT); try { if (securityInfo != null) { securityStore.put(securityInfo); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index cf158aa14e..3307db2fca 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -52,8 +52,6 @@ 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.LwM2MFirmwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.LwM2mOtaConvert; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; @@ -83,6 +81,8 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttrib import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteAttributesRequest; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MOtaUpdateService; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2MRpcRequestHandler; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -105,18 +105,18 @@ import java.util.concurrent.TimeUnit; 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.LwM2mTransportUtil.FW_3_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_VER_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_DELIVERY_METHOD; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_NAME_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_RESULT_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_STATE_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_ERROR; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_INFO; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LWM2M_WARN; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_3_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_5_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_DELIVERY_METHOD; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_NAME_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_RESULT_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.FW_STATE_ID; @Slf4j diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mTypeServer.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mTypeServer.java new file mode 100644 index 0000000000..0ecd59f41c --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mTypeServer.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.transport.lwm2m.server.uplink; + +public enum LwM2mTypeServer { + BOOTSTRAP(0, "bootstrap"), + CLIENT(1, "client"); + + public int code; + public String type; + + LwM2mTypeServer(int code, String type) { + this.code = code; + this.type = type; + } + + public static LwM2mTypeServer fromLwM2mTypeServer(String type) { + for (LwM2mTypeServer sm : LwM2mTypeServer.values()) { + if (sm.type.equals(type)) { + return sm; + } + } + throw new IllegalArgumentException(String.format("Unsupported typeServer type : %d", type)); + } +} + From d3e16ad65009f7c80869dae628bf3aba47685071 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Tue, 22 Jun 2021 13:31:08 +0300 Subject: [PATCH 452/476] updated observe sessions closing actions --- .../transport/coap/CoapTransportResource.java | 123 +++++++++++------- .../transport/http/DeviceApiController.java | 6 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 10 +- .../transport/mqtt/MqttTransportHandler.java | 5 +- .../mqtt/session/GatewayDeviceSessionCtx.java | 6 +- .../snmp/session/DeviceSessionContext.java | 6 +- .../common/transport/SessionMsgListener.java | 4 +- .../service/DefaultTransportService.java | 4 +- 8 files changed, 102 insertions(+), 62 deletions(-) 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 95cdeada88..9f5d78b5aa 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 @@ -64,6 +64,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; @Slf4j public class CoapTransportResource extends AbstractCoapTransportResource { @@ -75,9 +76,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private static final int REQUEST_ID_POSITION_CERTIFICATE_REQUEST = 4; private static final String DTLS_SESSION_ID_KEY = "DTLS_SESSION_ID"; - private final ConcurrentMap tokenToSessionInfoMap = new ConcurrentHashMap<>(); - private final ConcurrentMap tokenToObserveNotificationSeqMap = new ConcurrentHashMap<>(); - private final ConcurrentMap sessionInfoToObserveRelationMap = new ConcurrentHashMap<>(); + private final ConcurrentMap tokenToCoapSessionInfoMap = new ConcurrentHashMap<>(); + private final ConcurrentMap sessionInfoToObserveRelationMap = new ConcurrentHashMap<>(); private final Set rpcSubscriptions = ConcurrentHashMap.newKeySet(); private final Set attributeSubscriptions = ConcurrentHashMap.newKeySet(); @@ -93,7 +93,11 @@ public class CoapTransportResource extends AbstractCoapTransportResource { this.timeout = coapServerService.getTimeout(); this.sessionReportTimeout = ctx.getSessionReportTimeout(); ctx.getScheduler().scheduleAtFixedRate(() -> { - Set observeSessions = sessionInfoToObserveRelationMap.keySet(); + Set coapObserveSessionInfos = sessionInfoToObserveRelationMap.keySet(); + Set observeSessions = coapObserveSessionInfos + .stream() + .map(CoapObserveSessionInfo::getSessionInfoProto) + .collect(Collectors.toSet()); observeSessions.forEach(this::reportActivity); }, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); } @@ -111,17 +115,17 @@ public class CoapTransportResource extends AbstractCoapTransportResource { relation.setEstablished(); addObserveRelation(relation); } - AtomicInteger notificationCounter = tokenToObserveNotificationSeqMap.computeIfAbsent(token, s -> new AtomicInteger(0)); - response.getOptions().setObserve(notificationCounter.getAndIncrement()); + AtomicInteger observeNotificationCounter = tokenToCoapSessionInfoMap.get(token).getObserveNotificationCounter(); + response.getOptions().setObserve(observeNotificationCounter.getAndIncrement()); } // ObserveLayer takes care of the else case } - public void clearAndNotifyObserveRelation(ObserveRelation relation, CoAP.ResponseCode code) { + private void clearAndNotifyObserveRelation(ObserveRelation relation, CoAP.ResponseCode code) { relation.cancel(); relation.getExchange().sendResponse(new Response(code)); } - public Map getSessionInfoToObserveRelationMap() { + private Map getCoapSessionInfoToObserveRelationMap() { return sessionInfoToObserveRelationMap; } @@ -277,8 +281,8 @@ public class CoapTransportResource extends AbstractCoapTransportResource { new CoapOkCallback(exchange, CoAP.ResponseCode.CREATED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); break; case SUBSCRIBE_ATTRIBUTES_REQUEST: - TransportProtos.SessionInfoProto currentAttrSession = tokenToSessionInfoMap.get(getTokenFromRequest(request)); - if (currentAttrSession == null) { + CoapObserveSessionInfo currentCoapObserveAttrSessionInfo = tokenToCoapSessionInfoMap.get(getTokenFromRequest(request)); + if (currentCoapObserveAttrSessionInfo == null) { attributeSubscriptions.add(sessionId); registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); @@ -290,20 +294,20 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } break; case UNSUBSCRIBE_ATTRIBUTES_REQUEST: - TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(getTokenFromRequest(request)); - if (attrSession != null) { + CoapObserveSessionInfo coapObserveAttrSessionInfo = lookupAsyncSessionInfo(getTokenFromRequest(request)); + if (coapObserveAttrSessionInfo != null) { + TransportProtos.SessionInfoProto attrSession = coapObserveAttrSessionInfo.getSessionInfoProto(); UUID attrSessionId = toSessionId(attrSession); attributeSubscriptions.remove(attrSessionId); - sessionInfoToObserveRelationMap.remove(attrSession); transportService.process(attrSession, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().setUnsubscribe(true).build(), - new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); - closeAndDeregister(sessionInfo); + new CoapNoOpCallback(exchange)); } + closeAndDeregister(sessionInfo); break; case SUBSCRIBE_RPC_COMMANDS_REQUEST: - TransportProtos.SessionInfoProto currentRpcSession = tokenToSessionInfoMap.get(getTokenFromRequest(request)); - if (currentRpcSession == null) { + CoapObserveSessionInfo currentCoapObserveRpcSessionInfo = tokenToCoapSessionInfoMap.get(getTokenFromRequest(request)); + if (currentCoapObserveRpcSessionInfo == null) { rpcSubscriptions.add(sessionId); registerAsyncCoapSession(exchange, sessionInfo, coapTransportAdaptor, transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), getTokenFromRequest(request)); @@ -314,16 +318,16 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } break; case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: - TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(getTokenFromRequest(request)); - if (rpcSession != null) { + CoapObserveSessionInfo coapObserveRpcSessionInfo = lookupAsyncSessionInfo(getTokenFromRequest(request)); + if (coapObserveRpcSessionInfo != null) { + TransportProtos.SessionInfoProto rpcSession = coapObserveRpcSessionInfo.getSessionInfoProto(); UUID rpcSessionId = toSessionId(rpcSession); rpcSubscriptions.remove(rpcSessionId); - sessionInfoToObserveRelationMap.remove(rpcSession); transportService.process(rpcSession, TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), new CoapOkCallback(exchange, CoAP.ResponseCode.DELETED, CoAP.ResponseCode.INTERNAL_SERVER_ERROR)); - closeAndDeregister(sessionInfo); } + closeAndDeregister(sessionInfo); break; case TO_DEVICE_RPC_RESPONSE: transportService.process(sessionInfo, @@ -355,13 +359,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource { return new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); } - private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(String token) { - tokenToObserveNotificationSeqMap.remove(token); - return tokenToSessionInfoMap.remove(token); + private CoapObserveSessionInfo lookupAsyncSessionInfo(String token) { + return tokenToCoapSessionInfoMap.remove(token); } private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, String token) { - tokenToSessionInfoMap.putIfAbsent(token, sessionInfo); + tokenToCoapSessionInfoMap.putIfAbsent(token, new CoapObserveSessionInfo(sessionInfo)); transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder)); transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); } @@ -474,40 +477,33 @@ public class CoapTransportResource extends AbstractCoapTransportResource { } @Override - public void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg) { + public void onAttributeUpdate(UUID sessionId, TransportProtos.AttributeUpdateNotificationMsg msg) { + log.trace("[{}] Received attributes update notification to device", sessionId); try { exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg)); } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); - exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + closeAndDeregister(); } } @Override public void onRemoteSessionCloseCommand(UUID sessionId, TransportProtos.SessionCloseNotificationProto sessionCloseNotification) { log.trace("[{}] Received the remote command to close the session: {}", sessionId, sessionCloseNotification.getMessage()); - Map sessionToObserveRelationMap = coapTransportResource.getSessionInfoToObserveRelationMap(); - if (coapTransportResource.getObserverCount() > 0 && !CollectionUtils.isEmpty(sessionToObserveRelationMap)) { - Set observeSessions = sessionToObserveRelationMap.keySet(); - Optional observeSessionToClose = observeSessions.stream().filter(sessionInfoProto -> { - UUID observeSessionId = new UUID(sessionInfoProto.getSessionIdMSB(), sessionInfoProto.getSessionIdLSB()); - return observeSessionId.equals(sessionId); - }).findFirst(); - if (observeSessionToClose.isPresent()) { - TransportProtos.SessionInfoProto sessionInfoProto = observeSessionToClose.get(); - ObserveRelation observeRelation = sessionToObserveRelationMap.get(sessionInfoProto); - coapTransportResource.clearAndNotifyObserveRelation(observeRelation, CoAP.ResponseCode.SERVICE_UNAVAILABLE); - } - } + closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.SERVICE_UNAVAILABLE); + closeAndDeregister(); } @Override - public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { + public void onToDeviceRpcRequest(UUID sessionId, TransportProtos.ToDeviceRpcRequestMsg msg) { + log.trace("[{}] Received RPC command to device", sessionId); try { 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); + closeObserveRelationAndNotify(sessionId, CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + closeAndDeregister(); } } @@ -524,6 +520,30 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private boolean isConRequest() { return exchange.advanced().getRequest().isConfirmable(); } + + private void closeObserveRelationAndNotify(UUID sessionId, CoAP.ResponseCode responseCode) { + Map sessionToObserveRelationMap = coapTransportResource.getCoapSessionInfoToObserveRelationMap(); + if (coapTransportResource.getObserverCount() > 0 && !CollectionUtils.isEmpty(sessionToObserveRelationMap)) { + Optional observeSessionToClose = sessionToObserveRelationMap.keySet().stream().filter(coapObserveSessionInfo -> { + TransportProtos.SessionInfoProto sessionToDelete = coapObserveSessionInfo.getSessionInfoProto(); + UUID observeSessionId = new UUID(sessionToDelete.getSessionIdMSB(), sessionToDelete.getSessionIdLSB()); + return observeSessionId.equals(sessionId); + }).findFirst(); + if (observeSessionToClose.isPresent()) { + CoapObserveSessionInfo coapObserveSessionInfo = observeSessionToClose.get(); + ObserveRelation observeRelation = sessionToObserveRelationMap.get(coapObserveSessionInfo); + coapTransportResource.clearAndNotifyObserveRelation(observeRelation, responseCode); + } + } + } + + private void closeAndDeregister() { + Request request = exchange.advanced().getRequest(); + String token = coapTransportResource.getTokenFromRequest(request); + CoapObserveSessionInfo deleted = coapTransportResource.lookupAsyncSessionInfo(token); + coapTransportResource.closeAndDeregister(deleted.getSessionInfoProto()); + } + } public class CoapResourceObserver implements ResourceObserver { @@ -548,7 +568,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { public void addedObserveRelation(ObserveRelation relation) { Request request = relation.getExchange().getRequest(); String token = getTokenFromRequest(request); - sessionInfoToObserveRelationMap.putIfAbsent(tokenToSessionInfoMap.get(token), relation); + sessionInfoToObserveRelationMap.putIfAbsent(tokenToCoapSessionInfoMap.get(token), relation); log.trace("Added Observe relation for token: {}", token); } @@ -556,8 +576,7 @@ public class CoapTransportResource extends AbstractCoapTransportResource { public void removedObserveRelation(ObserveRelation relation) { Request request = relation.getExchange().getRequest(); String token = getTokenFromRequest(request); - TransportProtos.SessionInfoProto session = tokenToSessionInfoMap.get(token); - sessionInfoToObserveRelationMap.remove(session); + sessionInfoToObserveRelationMap.remove(tokenToCoapSessionInfoMap.get(token)); log.trace("Relation removed for token: {}", token); } } @@ -568,7 +587,6 @@ public class CoapTransportResource extends AbstractCoapTransportResource { transportService.deregisterSession(session); rpcSubscriptions.remove(sessionId); attributeSubscriptions.remove(sessionId); - sessionInfoToObserveRelationMap.remove(session); } private TransportConfigurationContainer getTransportConfigurationContainer(DeviceProfile deviceProfile) throws AdaptorException { @@ -634,4 +652,17 @@ public class CoapTransportResource extends AbstractCoapTransportResource { this.jsonPayload = jsonPayload; } } + + @Data + private static class CoapObserveSessionInfo { + + private final TransportProtos.SessionInfoProto sessionInfoProto; + private final AtomicInteger observeNotificationCounter; + + private CoapObserveSessionInfo(TransportProtos.SessionInfoProto sessionInfoProto) { + this.sessionInfoProto = sessionInfoProto; + this.observeNotificationCounter = new AtomicInteger(0); + } + } + } 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 f4a3f705af..79ff2b70d1 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 @@ -386,7 +386,8 @@ public class DeviceApiController implements TbTransportService { } @Override - public void onAttributeUpdate(AttributeUpdateNotificationMsg msg) { + public void onAttributeUpdate(UUID sessionId, AttributeUpdateNotificationMsg msg) { + log.trace("[{}] Received attributes update notification to device", sessionId); responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); } @@ -397,7 +398,8 @@ public class DeviceApiController implements TbTransportService { } @Override - public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg msg) { + public void onToDeviceRpcRequest(UUID sessionId, ToDeviceRpcRequestMsg msg) { + log.trace("[{}] Received RPC command to device", sessionId); responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK)); } 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 f39ced6dfc..b2059d9557 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 @@ -52,9 +52,10 @@ public class LwM2mSessionMsgListener implements GenericFutureListener Date: Tue, 22 Jun 2021 14:27:56 +0300 Subject: [PATCH 453/476] Update getOAuth2Clients Rest client method --- .../java/org/thingsboard/rest/client/RestClient.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) 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 6fbf28f24e..2d0914c7d4 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 @@ -104,6 +104,7 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.oauth2.OAuth2ClientInfo; import org.thingsboard.server.common.data.oauth2.OAuth2ClientRegistrationTemplate; import org.thingsboard.server.common.data.oauth2.OAuth2Info; +import org.thingsboard.server.common.data.oauth2.PlatformType; import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.page.PageData; @@ -1772,7 +1773,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { }).getBody(); } - public List getOAuth2Clients(String pkgName) { + public List getOAuth2Clients(String pkgName, PlatformType platformType) { Map params = new HashMap<>(); StringBuilder urlBuilder = new StringBuilder(baseURL); urlBuilder.append("/api/noauth/oauth2Clients"); @@ -1780,6 +1781,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { urlBuilder.append("?pkgName={pkgName}"); params.put("pkgName", pkgName); } + if (platformType != null) { + if (pkgName != null) { + urlBuilder.append("&"); + } else { + urlBuilder.append("?"); + } + urlBuilder.append("platform={platform}"); + params.put("platform", platformType.name()); + } return restTemplate.exchange( urlBuilder.toString(), HttpMethod.POST, From e83064ece73ec11726c131e56c47949f6d70e7e2 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 22 Jun 2021 15:53:06 +0300 Subject: [PATCH 454/476] Refactoring of the LwM2MTransport --- application/src/test/resources/logback.xml | 2 +- .../coap/OtaPackageTransportResource.java | 42 +------------ .../config/LwM2MTransportServerConfig.java | 2 +- ...LwM2mCredentialsSecurityInfoValidator.java | 5 +- .../lwm2m/server/LwM2mTransportUtil.java | 29 +++------ .../ota/DefaultLwM2MOtaUpdateService.java | 59 +++++++++---------- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 10 ++-- .../server/ota/LwM2MOtaUpdateService.java | 5 +- ...eryFw.java => FirmwareDeliveryMethod.java} | 19 ++---- ...esultFw.java => FirmwareUpdateResult.java} | 12 ++-- ...eStateFw.java => FirmwareUpdateState.java} | 12 ++-- ...esultSw.java => SoftwareUpdateResult.java} | 12 ++-- ...eStateSw.java => SoftwareUpdateState.java} | 12 ++-- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 44 +++++--------- 14 files changed, 98 insertions(+), 167 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/{UpdateDeliveryFw.java => FirmwareDeliveryMethod.java} (70%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/{UpdateResultFw.java => FirmwareUpdateResult.java} (86%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/{UpdateStateFw.java => FirmwareUpdateState.java} (80%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/{UpdateResultSw.java => SoftwareUpdateResult.java} (89%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/{UpdateStateSw.java => SoftwareUpdateState.java} (84%) diff --git a/application/src/test/resources/logback.xml b/application/src/test/resources/logback.xml index 69e5d98193..d3301bf660 100644 --- a/application/src/test/resources/logback.xml +++ b/application/src/test/resources/logback.xml @@ -14,7 +14,7 @@ - + diff --git a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java index 224dd61e2a..2aadea26b2 100644 --- a/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java +++ b/common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/OtaPackageTransportResource.java @@ -42,14 +42,12 @@ public class OtaPackageTransportResource extends AbstractCoapTransportResource { private static final int ACCESS_TOKEN_POSITION = 2; private final OtaPackageType otaPackageType; - private final ExecutorService sendOtaDataOutUriLarge; public OtaPackageTransportResource(CoapTransportContext ctx, OtaPackageType otaPackageType) { super(ctx, otaPackageType.getKeyPrefix()); this.otaPackageType = otaPackageType; this.setObservable(true); - this.sendOtaDataOutUriLarge = ThingsBoardExecutors.newWorkStealingPool(10, "LwM2M sendOtaDataOutUriLarge"); } @Override @@ -140,46 +138,12 @@ public class OtaPackageTransportResource extends AbstractCoapTransportResource { Response response = new Response(CoAP.ResponseCode.CONTENT); if (data != null && data.length > 0) { response.setPayload(data); -// response.getOptions().setAccept(MediaTypeRegistry.APPLICATION_OCTET_STREAM); if (exchange.getRequestOptions().getBlock2() != null) { int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); boolean lastFlag = data.length <= chunkSize; - this.sendOtaDataOutUriLarge.submit(() -> { - response.getOptions().setBlock2(chunkSize, lastFlag, 0); - }); } - exchange.respond(response); - } - } - - public class CoapResourceObserver implements ResourceObserver { - @Override - public void changedName(String old) { - - } - - @Override - public void changedPath(String old) { - - } - - @Override - public void addedChild(Resource child) { - - } - - @Override - public void removedChild(Resource child) { - - } - - @Override - public void addedObserveRelation(ObserveRelation relation) { - - } - - @Override - public void removedObserveRelation(ObserveRelation relation) { - + response.getOptions().setBlock2(chunkSize, lastFlag, 0); + } + transportContext.getExecutor().submit(() -> exchange.respond(response)); } } 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 a2ff361712..730effaf3c 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 @@ -142,7 +142,7 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { URI uri = null; try { uri = Resources.getResource(keyStorePathFile).toURI(); - log.error("URI: {}", uri); + log.info("URI: {}", uri); File keyStoreFile = new File(uri); InputStream inKeyStore = new FileInputStream(keyStoreFile); keyStoreValue = KeyStore.getInstance(keyStoreType); 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 8bd97a952e..d11f39c7f2 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 @@ -59,12 +59,12 @@ public class LwM2mCredentialsSecurityInfoValidator { public TbLwM2MSecurityInfo getEndpointSecurityInfoByCredentialsId(String credentialsId, LwM2mTypeServer keyValue) { CountDownLatch latch = new CountDownLatch(1); final TbLwM2MSecurityInfo[] resultSecurityStore = new TbLwM2MSecurityInfo[1]; - log.warn("001) [{}]", credentialsId); + log.trace("Validating credentials [{}]", credentialsId); context.getTransportService().process(ValidateDeviceLwM2MCredentialsRequestMsg.newBuilder().setCredentialsId(credentialsId).build(), new TransportServiceCallback<>() { @Override public void onSuccess(ValidateDeviceCredentialsResponse msg) { - log.warn("002) [{}] [{}]", credentialsId, msg); + log.trace("Validated credentials: [{}] [{}]", credentialsId, msg); String credentialsBody = msg.getCredentials(); resultSecurityStore[0] = createSecurityInfo(credentialsId, credentialsBody, keyValue); resultSecurityStore[0].setMsg(msg); @@ -74,7 +74,6 @@ public class LwM2mCredentialsSecurityInfoValidator { @Override public void onError(Throwable e) { - log.warn("003) [{}] [{}] Failed to process credentials ", credentialsId, e); log.trace("[{}] [{}] Failed to process credentials ", credentialsId, e); resultSecurityStore[0] = createSecurityInfo(credentialsId, null, null); latch.countDown(); 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 64f80f7353..bbab28ded5 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 @@ -46,10 +46,10 @@ import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTrans import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateResultFw; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateStateFw; -import org.thingsboard.server.transport.lwm2m.server.ota.software.UpdateResultSw; -import org.thingsboard.server.transport.lwm2m.server.ota.software.UpdateStateSw; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; +import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; +import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import java.util.ArrayList; @@ -148,19 +148,19 @@ public class LwM2mTransportUtil { if (path != null) { if (FW_STATE_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); - lwM2mOtaConvert.setValue(UpdateStateFw.fromStateFwByCode(((Long) value).intValue()).type); + lwM2mOtaConvert.setValue(FirmwareUpdateState.fromStateFwByCode(((Long) value).intValue()).type); return lwM2mOtaConvert; } else if (FW_RESULT_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); - lwM2mOtaConvert.setValue(UpdateResultFw.fromUpdateResultFwByCode(((Long) value).intValue()).getType()); + lwM2mOtaConvert.setValue(FirmwareUpdateResult.fromUpdateResultFwByCode(((Long) value).intValue()).getType()); return lwM2mOtaConvert; } else if (SW_UPDATE_STATE_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); - lwM2mOtaConvert.setValue(UpdateStateSw.fromUpdateStateSwByCode(((Long) value).intValue()).type); + lwM2mOtaConvert.setValue(SoftwareUpdateState.fromUpdateStateSwByCode(((Long) value).intValue()).type); return lwM2mOtaConvert; } else if (SW_RESULT_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); - lwM2mOtaConvert.setValue(UpdateResultSw.fromUpdateResultSwByCode(((Long) value).intValue()).type); + lwM2mOtaConvert.setValue(SoftwareUpdateResult.fromUpdateResultSwByCode(((Long) value).intValue()).type); return lwM2mOtaConvert; } } @@ -435,19 +435,6 @@ public class LwM2mTransportUtil { } } -// public static boolean isFwSwWords(String pathName) { -// return OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.VERSION).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.TITLE).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.FIRMWARE, OtaPackageKey.SIZE).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.VERSION).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.TITLE).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.CHECKSUM_ALGORITHM).equals(pathName) -// || OtaPackageUtil.getAttributeKey(OtaPackageType.SOFTWARE, OtaPackageKey.SIZE).equals(pathName); -// } - /** * @param lwM2MClient - * @param path - diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java index 8f0a9da33f..d3f1e8eb9d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/DefaultLwM2MOtaUpdateService.java @@ -25,6 +25,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; import org.thingsboard.server.common.data.ota.OtaPackageKey; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; @@ -45,12 +46,12 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplac import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteResponseCallback; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateDeliveryFw; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateResultFw; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareDeliveryMethod; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.ota.software.UpdateResultSw; -import org.thingsboard.server.transport.lwm2m.server.ota.software.UpdateStateSw; +import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; +import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import javax.annotation.PostConstruct; @@ -201,20 +202,20 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } @Override - public void onCurrentFirmwareStrategyUpdate(LwM2mClient client, Integer newStrategy, String newBaseUrl) { - log.debug("[{}] Current fw strategy: {}", client.getEndpoint(), newStrategy); + public void onCurrentFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) { + log.debug("[{}] Current fw strategy: {}", client.getEndpoint(), configuration.getFwUpdateStrategy()); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); - fwInfo.setFwStrategy(LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(newStrategy)); - fwInfo.setBaseUrl(newBaseUrl); + fwInfo.setFwStrategy(LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(configuration.getFwUpdateStrategy())); + fwInfo.setBaseUrl(configuration.getFwUpdateRecourse()); startFirmwareUpdateIfNeeded(client, fwInfo); } @Override - public void onCurrentSoftwareStrategyUpdate(LwM2mClient client, Integer newStrategy, String newBaseUrl) { - log.debug("[{}] Current sw strategy: {}", client.getEndpoint(), newStrategy); + public void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) { + log.debug("[{}] Current sw strategy: {}", client.getEndpoint(), configuration.getSwUpdateStrategy()); LwM2MClientOtaInfo swInfo = getOrInitSwInfo(client); - swInfo.setSwStrategy(LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(newStrategy)); - swInfo.setBaseUrl(newBaseUrl); + swInfo.setSwStrategy(LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(configuration.getSwUpdateStrategy())); + swInfo.setBaseUrl(configuration.getSwUpdateRecourse()); startSoftwareUpdateIfNeeded(client, swInfo); } @@ -236,8 +237,8 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long stateCode) { log.debug("[{}] Current fw state: {}", client.getEndpoint(), stateCode); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); - UpdateStateFw state = UpdateStateFw.fromStateFwByCode(stateCode.intValue()); - if (UpdateStateFw.DOWNLOADED.equals(state)) { + FirmwareUpdateState state = FirmwareUpdateState.fromStateFwByCode(stateCode.intValue()); + if (FirmwareUpdateState.DOWNLOADED.equals(state)) { executeFwUpdate(client); } fwInfo.setUpdateState(state); @@ -250,7 +251,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl public void onCurrentFirmwareResultUpdate(LwM2mClient client, Long code) { log.debug("[{}] Current fw result: {}", client.getEndpoint(), code); LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); - UpdateResultFw result = UpdateResultFw.fromUpdateResultFwByCode(code.intValue()); + FirmwareUpdateResult result = FirmwareUpdateResult.fromUpdateResultFwByCode(code.intValue()); Optional status = this.toOtaPackageUpdateStatus(result); status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, otaStatus, "Firmware Update Result: " + result.name())); @@ -324,10 +325,10 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); LwM2MFirmwareUpdateStrategy strategy; - if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == UpdateDeliveryFw.BOTH.code) { + if (fwInfo.getDeliveryMethod() == null || fwInfo.getDeliveryMethod() == FirmwareDeliveryMethod.BOTH.code) { strategy = fwInfo.getFwStrategy(); } else { - strategy = fwInfo.getDeliveryMethod() == UpdateDeliveryFw.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; + strategy = fwInfo.getDeliveryMethod() == FirmwareDeliveryMethod.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; } switch (strategy) { case OBJ_5_BINARY: @@ -405,8 +406,8 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl helper.sendParametersOnThingsboardTelemetry(result, client.getSession()); } - private static Optional toOtaPackageUpdateStatus(UpdateResultFw updateResultFw) { - switch (updateResultFw) { + private static Optional toOtaPackageUpdateStatus(FirmwareUpdateResult fwUpdateResult) { + switch (fwUpdateResult) { case INITIAL: return Optional.empty(); case UPDATE_SUCCESSFULLY: @@ -421,12 +422,12 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl case UNSUPPORTED_PROTOCOL: return Optional.of(FAILED); default: - throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", updateResultFw.name()); + throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", fwUpdateResult.name()); } } - private static Optional toOtaPackageUpdateStatus(UpdateStateFw updateStateFw) { - switch (updateStateFw) { + private static Optional toOtaPackageUpdateStatus(FirmwareUpdateState firmwareUpdateState) { + switch (firmwareUpdateState) { case IDLE: return Optional.empty(); case DOWNLOADING: @@ -436,7 +437,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl case UPDATING: return Optional.of(UPDATING); default: - throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", updateStateFw); + throw new CodecException("Invalid value stateFw %d for FirmwareUpdateStatus.", firmwareUpdateState); } } @@ -444,10 +445,10 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl * FirmwareUpdateStatus { * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED */ - public static Optional toSwSateResultUpdateStatus(UpdateStateSw updateStateSw, UpdateResultSw updateResultSw) { - switch (updateResultSw) { + public static Optional toSwSateResultUpdateStatus(SoftwareUpdateState softwareUpdateState, SoftwareUpdateResult softwareUpdateResult) { + switch (softwareUpdateResult) { case INITIAL: - switch (updateStateSw) { + switch (softwareUpdateState) { case INITIAL: case DOWNLOAD_STARTED: return Optional.of(DOWNLOADING); @@ -473,10 +474,8 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl case UN_INSTALL_FAILURE: return Optional.of(FAILED); default: - throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", updateStateSw.name(), updateResultSw.name()); + throw new CodecException("Invalid value stateFw %s %s for FirmwareUpdateStatus.", softwareUpdateState.name(), softwareUpdateResult.name()); } } - - } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java index 98e2055fdc..2b503ef8a4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MClientOtaInfo.java @@ -19,8 +19,8 @@ import lombok.Data; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateResultFw; -import org.thingsboard.server.transport.lwm2m.server.ota.firmware.UpdateStateFw; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateResult; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.FirmwareUpdateState; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; import java.util.Optional; @@ -47,8 +47,8 @@ public class LwM2MClientOtaInfo { //TODO: use value from device if applicable; private LwM2MFirmwareUpdateStrategy fwStrategy; private LwM2MSoftwareUpdateStrategy swStrategy; - private UpdateStateFw updateState; - private UpdateResultFw updateResult; + private FirmwareUpdateState updateState; + private FirmwareUpdateResult updateResult; private String failedPackageId; private int retryAttempts; @@ -90,7 +90,7 @@ public class LwM2MClientOtaInfo { return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion5) || StringUtils.isNotEmpty(currentVersion3); } - public void setUpdateResult(UpdateResultFw updateResult) { + public void setUpdateResult(FirmwareUpdateResult updateResult) { this.updateResult = updateResult; switch (updateResult) { case INITIAL: diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java index 644099ee9d..3ba1b1500a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/LwM2MOtaUpdateService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.ota; +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import java.util.Optional; @@ -31,9 +32,9 @@ public interface LwM2MOtaUpdateService { void onCurrentFirmwareNameUpdate(LwM2mClient client, String name); - void onCurrentFirmwareStrategyUpdate(LwM2mClient client, Integer newStrategy, String newBaseUrl); + void onCurrentFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration); - void onCurrentSoftwareStrategyUpdate(LwM2mClient client, Integer newStrategy, String newBaseUrl); + void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration); void onCurrentFirmwareVersion3Update(LwM2mClient client, String version); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateDeliveryFw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareDeliveryMethod.java similarity index 70% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateDeliveryFw.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareDeliveryMethod.java index dcb93a5283..4df6367d6f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateDeliveryFw.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareDeliveryMethod.java @@ -15,14 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.ota.firmware; -/** - * /** Delivery Method R - * 0: Pull only - * 1: Push only - * 2: Both: - * - In this case the LwM2M Server MAY choose the preferred mechanism for conveying the firmware image to the LwM2M Client. - */ -public enum UpdateDeliveryFw { +public enum FirmwareDeliveryMethod { PULL(0, "Pull only"), PUSH(1, "Push only"), BOTH(2, "Push or Push"); @@ -30,13 +23,13 @@ public enum UpdateDeliveryFw { public int code; public String type; - UpdateDeliveryFw(int code, String type) { + FirmwareDeliveryMethod(int code, String type) { this.code = code; this.type = type; } - public static UpdateDeliveryFw fromStateFwByType(String type) { - for (UpdateDeliveryFw to : UpdateDeliveryFw.values()) { + public static FirmwareDeliveryMethod fromStateFwByType(String type) { + for (FirmwareDeliveryMethod to : FirmwareDeliveryMethod.values()) { if (to.type.equals(type)) { return to; } @@ -44,8 +37,8 @@ public enum UpdateDeliveryFw { throw new IllegalArgumentException(String.format("Unsupported FW delivery type : %s", type)); } - public static UpdateDeliveryFw fromStateFwByCode(int code) { - for (UpdateDeliveryFw to : UpdateDeliveryFw.values()) { + public static FirmwareDeliveryMethod fromStateFwByCode(int code) { + for (FirmwareDeliveryMethod to : FirmwareDeliveryMethod.values()) { if (to.code == code) { return to; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateResultFw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateResult.java similarity index 86% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateResultFw.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateResult.java index 5a71f41bc5..cdb7d71f25 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateResultFw.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateResult.java @@ -30,7 +30,7 @@ import lombok.Getter; * 8: Firmware update failed. * 9: Unsupported protocol. */ -public enum UpdateResultFw { +public enum FirmwareUpdateResult { INITIAL(0, "Initial value", false), UPDATE_SUCCESSFULLY(1, "Firmware updated successfully", false), NOT_ENOUGH(2, "Not enough flash memory for the new firmware package", false), @@ -49,14 +49,14 @@ public enum UpdateResultFw { @Getter private boolean again; - UpdateResultFw(int code, String type, boolean isAgain) { + FirmwareUpdateResult(int code, String type, boolean isAgain) { this.code = code; this.type = type; this.again = isAgain; } - public static UpdateResultFw fromUpdateResultFwByType(String type) { - for (UpdateResultFw to : UpdateResultFw.values()) { + public static FirmwareUpdateResult fromUpdateResultFwByType(String type) { + for (FirmwareUpdateResult to : FirmwareUpdateResult.values()) { if (to.type.equals(type)) { return to; } @@ -64,8 +64,8 @@ public enum UpdateResultFw { throw new IllegalArgumentException(String.format("Unsupported FW Update Result type : %s", type)); } - public static UpdateResultFw fromUpdateResultFwByCode(int code) { - for (UpdateResultFw to : UpdateResultFw.values()) { + public static FirmwareUpdateResult fromUpdateResultFwByCode(int code) { + for (FirmwareUpdateResult to : FirmwareUpdateResult.values()) { if (to.code == code) { return to; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateStateFw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java similarity index 80% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateStateFw.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java index 447749fd50..ddd04fbfd0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/UpdateStateFw.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/FirmwareUpdateState.java @@ -22,7 +22,7 @@ package org.thingsboard.server.transport.lwm2m.server.ota.firmware; * 2: Downloaded * 3: Updating */ -public enum UpdateStateFw { +public enum FirmwareUpdateState { IDLE(0, "Idle"), DOWNLOADING(1, "Downloading"), DOWNLOADED(2, "Downloaded"), @@ -31,13 +31,13 @@ public enum UpdateStateFw { public int code; public String type; - UpdateStateFw(int code, String type) { + FirmwareUpdateState(int code, String type) { this.code = code; this.type = type; } - public static UpdateStateFw fromStateFwByType(String type) { - for (UpdateStateFw to : UpdateStateFw.values()) { + public static FirmwareUpdateState fromStateFwByType(String type) { + for (FirmwareUpdateState to : FirmwareUpdateState.values()) { if (to.type.equals(type)) { return to; } @@ -45,8 +45,8 @@ public enum UpdateStateFw { throw new IllegalArgumentException(String.format("Unsupported FW State type : %s", type)); } - public static UpdateStateFw fromStateFwByCode(int code) { - for (UpdateStateFw to : UpdateStateFw.values()) { + public static FirmwareUpdateState fromStateFwByCode(int code) { + for (FirmwareUpdateState to : FirmwareUpdateState.values()) { if (to.code == code) { return to; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateResultSw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java similarity index 89% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateResultSw.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java index 22c92205d0..e99d4e4aca 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateResultSw.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java @@ -39,7 +39,7 @@ package org.thingsboard.server.transport.lwm2m.server.ota.software; * 60-200 : (for expansion, selection to be in blocks depending on new introduction of features) * This Resource MAY be reported by sending Observe operation. */ -public enum UpdateResultSw { +public enum SoftwareUpdateResult { INITIAL(0, "Initial value", false), DOWNLOADING(1, "Downloading", false), SUCCESSFULLY_INSTALLED(2, "Software successfully installed", false), @@ -58,14 +58,14 @@ public enum UpdateResultSw { public String type; public boolean isAgain; - UpdateResultSw(int code, String type, boolean isAgain) { + SoftwareUpdateResult(int code, String type, boolean isAgain) { this.code = code; this.type = type; this.isAgain = isAgain; } - public static UpdateResultSw fromUpdateResultSwByType(String type) { - for (UpdateResultSw to : UpdateResultSw.values()) { + public static SoftwareUpdateResult fromUpdateResultSwByType(String type) { + for (SoftwareUpdateResult to : SoftwareUpdateResult.values()) { if (to.type.equals(type)) { return to; } @@ -73,8 +73,8 @@ public enum UpdateResultSw { throw new IllegalArgumentException(String.format("Unsupported SW Update Result type : %s", type)); } - public static UpdateResultSw fromUpdateResultSwByCode(int code) { - for (UpdateResultSw to : UpdateResultSw.values()) { + public static SoftwareUpdateResult fromUpdateResultSwByCode(int code) { + for (SoftwareUpdateResult to : SoftwareUpdateResult.values()) { if (to.code == code) { return to; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateStateSw.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java similarity index 84% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateStateSw.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java index a5f772a11d..394d2e501a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/UpdateStateSw.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateState.java @@ -26,7 +26,7 @@ package org.thingsboard.server.transport.lwm2m.server.ota.software; * After executing the UnInstall Resource, the state changes to INITIAL. * 4: INSTALLED */ -public enum UpdateStateSw { +public enum SoftwareUpdateState { INITIAL(0, "Initial"), DOWNLOAD_STARTED(1, "DownloadStarted"), DOWNLOADED(2, "Downloaded"), @@ -36,13 +36,13 @@ public enum UpdateStateSw { public int code; public String type; - UpdateStateSw(int code, String type) { + SoftwareUpdateState(int code, String type) { this.code = code; this.type = type; } - public static UpdateStateSw fromUpdateStateSwByType(String type) { - for (UpdateStateSw to : UpdateStateSw.values()) { + public static SoftwareUpdateState fromUpdateStateSwByType(String type) { + for (SoftwareUpdateState to : SoftwareUpdateState.values()) { if (to.type.equals(type)) { return to; } @@ -50,8 +50,8 @@ public enum UpdateStateSw { throw new IllegalArgumentException(String.format("Unsupported SW State type : %s", type)); } - public static UpdateStateSw fromUpdateStateSwByCode(int code) { - for (UpdateStateSw to : UpdateStateSw.values()) { + public static SoftwareUpdateState fromUpdateStateSwByCode(int code) { + for (SoftwareUpdateState to : SoftwareUpdateState.values()) { if (to.code == code) { return to; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 3307db2fca..767c316cc6 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java @@ -40,7 +40,9 @@ import org.thingsboard.common.util.DonAsynchron; import org.thingsboard.server.cache.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.ota.OtaPackageUtil; @@ -130,16 +132,13 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl private final LwM2mTransportContext context; private final LwM2MAttributesService attributesService; private final LwM2MOtaUpdateService otaService; - public final LwM2MTransportServerConfig config; + private final LwM2MTransportServerConfig config; private final LwM2MTelemetryLogService logService; - public final OtaPackageDataCache otaPackageDataCache; - public final LwM2mTransportServerHelper helper; + private final LwM2mTransportServerHelper helper; private final TbLwM2MDtlsSessionStore sessionStore; - public final LwM2mClientContext clientContext; + private final LwM2mClientContext clientContext; private final LwM2MRpcRequestHandler rpcHandler; - public final LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler; - - public final Map firmwareUpdateState; + private final LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler; public DefaultLwM2MUplinkMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, @@ -150,7 +149,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl @Lazy LwM2MAttributesService attributesService, @Lazy LwM2MRpcRequestHandler rpcHandler, @Lazy LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler, - OtaPackageDataCache otaPackageDataCache, LwM2mTransportContext context, TbLwM2MDtlsSessionStore sessionStore) { this.transportService = transportService; this.attributesService = attributesService; @@ -161,9 +159,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl this.logService = logService; this.rpcHandler = rpcHandler; this.defaultLwM2MDownlinkMsgHandler = defaultLwM2MDownlinkMsgHandler; - this.otaPackageDataCache = otaPackageDataCache; this.context = context; - this.firmwareUpdateState = new ConcurrentHashMap<>(); this.sessionStore = sessionStore; } @@ -781,27 +777,19 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl } } - // # 7.1 // update value in fwInfo - if (!newProfile.getClientLwM2mSettings().getFwUpdateStrategy().equals(oldProfile.getClientLwM2mSettings().getFwUpdateStrategy()) - || (LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == newProfile.getClientLwM2mSettings().getFwUpdateStrategy() && - !newProfile.getClientLwM2mSettings().getFwUpdateRecourse().equals(oldProfile.getClientLwM2mSettings().getFwUpdateRecourse()))) { - clients.forEach(lwM2MClient -> { - otaService.onCurrentFirmwareStrategyUpdate(lwM2MClient, - newProfile.getClientLwM2mSettings().getFwUpdateStrategy(), - newProfile.getClientLwM2mSettings().getFwUpdateRecourse()); - }); + OtherConfiguration newLwM2mSettings = newProfile.getClientLwM2mSettings(); + OtherConfiguration oldLwM2mSettings = oldProfile.getClientLwM2mSettings(); + if (!newLwM2mSettings.getFwUpdateStrategy().equals(oldLwM2mSettings.getFwUpdateStrategy()) + || (StringUtils.isNotEmpty(newLwM2mSettings.getFwUpdateRecourse()) && + !newLwM2mSettings.getFwUpdateRecourse().equals(oldLwM2mSettings.getFwUpdateRecourse()))) { + clients.forEach(lwM2MClient -> otaService.onCurrentFirmwareStrategyUpdate(lwM2MClient, newLwM2mSettings)); } - //# 7.2 // update value in swInfo - if (!newProfile.getClientLwM2mSettings().getSwUpdateStrategy().equals(oldProfile.getClientLwM2mSettings().getSwUpdateStrategy()) - || (LwM2MSoftwareUpdateStrategy.TEMP_URL.code == newProfile.getClientLwM2mSettings().getSwUpdateStrategy() && - !newProfile.getClientLwM2mSettings().getSwUpdateRecourse().equals(oldProfile.getClientLwM2mSettings().getSwUpdateRecourse()))) { - clients.forEach(lwM2MClient -> { - otaService.onCurrentSoftwareStrategyUpdate(lwM2MClient, - newProfile.getClientLwM2mSettings().getFwUpdateStrategy(), - newProfile.getClientLwM2mSettings().getFwUpdateRecourse()); - }); + if (!newLwM2mSettings.getSwUpdateStrategy().equals(oldLwM2mSettings.getSwUpdateStrategy()) + || (StringUtils.isNotEmpty(newLwM2mSettings.getSwUpdateRecourse()) && + !newLwM2mSettings.getSwUpdateRecourse().equals(oldLwM2mSettings.getSwUpdateRecourse()))) { + clients.forEach(lwM2MClient -> otaService.onCurrentSoftwareStrategyUpdate(lwM2MClient, newLwM2mSettings)); } } } From a44bc1ba93589e5df89a900adc8e63c9f026f4d4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 22 Jun 2021 16:21:56 +0300 Subject: [PATCH 455/476] added SendPendingRPC transport msg --- .../device/DeviceActorMessageProcessor.java | 21 ++++++++++++------- common/queue/src/main/proto/queue.proto | 14 ++++++++----- 2 files changed, 22 insertions(+), 13 deletions(-) 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 a1458fd5ac..2d5f107d61 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 @@ -322,29 +322,34 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { void process(TbActorCtx context, TransportToDeviceActorMsgWrapper wrapper) { TransportToDeviceActorMsg msg = wrapper.getMsg(); TbCallback callback = wrapper.getCallback(); + var sessionInfo = msg.getSessionInfo(); + if (msg.hasSessionEvent()) { - processSessionStateMsgs(msg.getSessionInfo(), msg.getSessionEvent()); + processSessionStateMsgs(sessionInfo, msg.getSessionEvent()); } if (msg.hasSubscribeToAttributes()) { - processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToAttributes()); + processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToAttributes()); } if (msg.hasSubscribeToRPC()) { - processSubscriptionCommands(context, msg.getSessionInfo(), msg.getSubscribeToRPC()); + processSubscriptionCommands(context, sessionInfo, msg.getSubscribeToRPC()); + } + if (msg.hasSendPendingRPC()) { + sendPendingRequests(context, getSessionId(sessionInfo), sessionInfo); } if (msg.hasGetAttributes()) { - handleGetAttributesRequest(context, msg.getSessionInfo(), msg.getGetAttributes()); + handleGetAttributesRequest(context, sessionInfo, msg.getGetAttributes()); } if (msg.hasToDeviceRPCCallResponse()) { - processRpcResponses(context, msg.getSessionInfo(), msg.getToDeviceRPCCallResponse()); + processRpcResponses(context, sessionInfo, msg.getToDeviceRPCCallResponse()); } if (msg.hasSubscriptionInfo()) { - handleSessionActivity(context, msg.getSessionInfo(), msg.getSubscriptionInfo()); + handleSessionActivity(context, sessionInfo, msg.getSubscriptionInfo()); } if (msg.hasClaimDevice()) { - handleClaimDeviceMsg(context, msg.getSessionInfo(), msg.getClaimDevice()); + handleClaimDeviceMsg(context, sessionInfo, msg.getClaimDevice()); } if (msg.hasPersistedRpcResponseMsg()) { - processPersistedRpcResponses(context, msg.getSessionInfo(), msg.getPersistedRpcResponseMsg()); + processPersistedRpcResponses(context, sessionInfo, msg.getPersistedRpcResponseMsg()); } callback.onSuccess(); } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index a7edd42142..d47e6838d3 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -318,6 +318,9 @@ message SubscribeToRPCMsg { SessionType sessionType = 2; } +message SendPendingRPCMsg { +} + message ToDeviceRpcRequestMsg { int32 requestId = 1; string methodName = 2; @@ -440,11 +443,12 @@ message TransportToDeviceActorMsg { GetAttributeRequestMsg getAttributes = 3; SubscribeToAttributeUpdatesMsg subscribeToAttributes = 4; SubscribeToRPCMsg subscribeToRPC = 5; - ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6; - SubscriptionInfoProto subscriptionInfo = 7; - ClaimDeviceMsg claimDevice = 8; - ProvisionDeviceRequestMsg provisionDevice = 9; - ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 10; + SendPendingRPCMsg sendPendingRPC = 6; + ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 7; + SubscriptionInfoProto subscriptionInfo = 8; + ClaimDeviceMsg claimDevice = 9; + ProvisionDeviceRequestMsg provisionDevice = 10; + ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 11; } message TransportToRuleEngineMsg { From 74d2a3ebaf124d5a3be07055229b697c06647226 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 22 Jun 2021 16:56:26 +0300 Subject: [PATCH 456/476] UI: Refactoring LWM2M device profile transport configuration Servers tab --- .../app/core/http/device-profile.service.ts | 42 ++++++++++++--- ...ofile-transport-configuration.component.ts | 5 +- .../lwm2m-device-config-server.component.html | 26 ++++++++- .../lwm2m-device-config-server.component.ts | 54 ++++++------------- ...ile-transport-configuration.component.html | 41 ++++++++++---- ...ofile-transport-configuration.component.ts | 46 ++++++++++++---- .../lwm2m/lwm2m-profile-config.models.ts | 44 ++++++--------- .../assets/locale/locale.constant-en_US.json | 16 ++++-- 8 files changed, 171 insertions(+), 103 deletions(-) diff --git a/ui-ngx/src/app/core/http/device-profile.service.ts b/ui-ngx/src/app/core/http/device-profile.service.ts index 6107a0853e..26e68a2e88 100644 --- a/ui-ngx/src/app/core/http/device-profile.service.ts +++ b/ui-ngx/src/app/core/http/device-profile.service.ts @@ -21,18 +21,23 @@ import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { Observable, of, throwError } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; import { DeviceProfile, DeviceProfileInfo, DeviceTransportType } from '@shared/models/device.models'; -import { isDefinedAndNotNull, isEmptyStr } from '@core/utils'; -import { ObjectLwM2M, ServerSecurityConfig } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; +import { deepClone, isDefinedAndNotNull, isEmptyStr } from '@core/utils'; +import { + ObjectLwM2M, + securityConfigMode, + ServerSecurityConfig, + ServerSecurityConfigInfo +} from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; import { SortOrder } from '@shared/models/page/sort-order'; import { OtaPackageService } from '@core/http/ota-package.service'; -import { mergeMap, tap } from 'rxjs/operators'; +import { map, mergeMap, tap } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class DeviceProfileService { - private lwm2mBootstrapSecurityInfoInMemoryCache = new Map(); + private lwm2mBootstrapSecurityInfoInMemoryCache = new Map(); constructor( private http: HttpClient, @@ -60,12 +65,12 @@ export class DeviceProfileService { return this.http.get>(url, defaultHttpOptionsFromConfig(config)); } - public getLwm2mBootstrapSecurityInfo(isBootstrapServer: boolean, config?: RequestConfig): Observable { + public getLwm2mBootstrapSecurityInfo(isBootstrapServer: boolean, config?: RequestConfig): Observable { const securityConfig = this.lwm2mBootstrapSecurityInfoInMemoryCache.get(isBootstrapServer); if (securityConfig) { return of(securityConfig); } else { - return this.http.get( + return this.http.get( `/api/lwm2m/deviceProfile/bootstrap/${isBootstrapServer}`, defaultHttpOptionsFromConfig(config) ).pipe( @@ -74,6 +79,31 @@ export class DeviceProfileService { } } + public getLwm2mBootstrapSecurityInfoBySecurityType(isBootstrapServer: boolean, securityMode = securityConfigMode.NO_SEC, + config?: RequestConfig): Observable { + return this.getLwm2mBootstrapSecurityInfo(isBootstrapServer, config).pipe( + map(securityConfig => { + const serverSecurityConfigInfo = deepClone(securityConfig); + switch (securityMode) { + case securityConfigMode.PSK: + serverSecurityConfigInfo.port = serverSecurityConfigInfo.securityPort; + serverSecurityConfigInfo.host = serverSecurityConfigInfo.securityHost; + serverSecurityConfigInfo.serverPublicKey = ''; + break; + case securityConfigMode.RPK: + case securityConfigMode.X509: + serverSecurityConfigInfo.port = serverSecurityConfigInfo.securityPort; + serverSecurityConfigInfo.host = serverSecurityConfigInfo.securityHost; + break; + case securityConfigMode.NO_SEC: + serverSecurityConfigInfo.serverPublicKey = ''; + break; + } + return serverSecurityConfigInfo; + }) + ); + } + public getLwm2mObjectsPage(pageLink: PageLink, config?: RequestConfig): Observable> { return this.http.get>( `/api/resource/lwm2m/page${pageLink.toQuery()}`, diff --git a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts index 50b3bc801c..f5eb23e9e3 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/device-profile-transport-configuration.component.ts @@ -89,9 +89,10 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu if (configuration) { delete configuration.type; } + this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); setTimeout(() => { - this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); - }); + this.deviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + }, 0); } private updateModel() { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html index 78f08ce31a..3a8ac64658 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-config-server.component.html @@ -35,19 +35,33 @@ {{ 'device-profile.lwm2m.server-port' | translate }} - + {{ 'device-profile.lwm2m.server-port-required' | translate }} + + {{ 'device-profile.lwm2m.server-port-pattern' | translate }} + + + {{ 'device-profile.lwm2m.server-port-range' | translate }} +
{{ 'device-profile.lwm2m.short-id' | translate }} - + {{ 'device-profile.lwm2m.short-id-required' | translate }} + + {{ 'device-profile.lwm2m.short-id-pattern' | translate }} + + + {{ 'device-profile.lwm2m.short-id-range' | translate }} + {{ 'device-profile.lwm2m.client-hold-off-time' | translate }} @@ -57,6 +71,10 @@ {{ 'device-profile.lwm2m.client-hold-off-time-required' | translate }} + + {{ 'device-profile.lwm2m.client-hold-off-time-pattern' | translate }} + {{ 'device-profile.lwm2m.account-after-timeout' | translate }} @@ -66,6 +84,10 @@ {{ 'device-profile.lwm2m.account-after-timeout-required' | translate }} + + {{ 'device-profile.lwm2m.account-after-timeout-pattern' | translate }} +
+
{{ 'device-profile.lwm2m.short-id' | translate }} - + {{ 'device-profile.lwm2m.short-id-required' | translate }} + + {{ 'device-profile.lwm2m.short-id-range' | translate }} + + + {{ 'device-profile.lwm2m.short-id-pattern' | translate }} + {{ 'device-profile.lwm2m.lifetime' | translate }} @@ -56,6 +63,10 @@ {{ 'device-profile.lwm2m.lifetime-required' | translate }} + + {{ 'device-profile.lwm2m.lifetime-pattern' | translate }} + {{ 'device-profile.lwm2m.default-min-period' | translate }} @@ -63,6 +74,10 @@ {{ 'device-profile.lwm2m.default-min-period-required' | translate }} + + {{ 'device-profile.lwm2m.default-min-period-pattern' | translate }} +
@@ -82,19 +97,23 @@ {{ 'device-profile.lwm2m.bootstrap-server' | translate }} - - + + + + {{ 'device-profile.lwm2m.lwm2m-server' | translate }} - - + + + + @@ -148,7 +167,7 @@ - + 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 ad53bd987f..2d330620ef 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 @@ -29,7 +29,10 @@ import { DEFAULT_MIN_PERIOD, DEFAULT_NOTIF_IF_DESIBLED, DEFAULT_SW_UPDATE_RESOURCE, - getDefaultProfileConfig, + getDefaultBootstrapServerSecurityConfig, + getDefaultBootstrapServersSecurityConfig, getDefaultLwM2MServerSecurityConfig, + getDefaultProfileClientLwM2mSettingsConfig, + getDefaultProfileObserveAttrConfig, Instance, INSTANCES, KEY_NAME, @@ -38,7 +41,7 @@ import { ObjectLwM2M, OBSERVE, OBSERVE_ATTR_TELEMETRY, - RESOURCES, + RESOURCES, ServerSecurityConfig, TELEMETRY } from './lwm2m-profile-config.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; @@ -94,16 +97,16 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro bootstrap: this.fb.group({ servers: this.fb.group({ binding: [DEFAULT_BINDING], - shortId: [DEFAULT_ID_SERVER, [Validators.required, Validators.min(0)]], - lifetime: [DEFAULT_LIFE_TIME, [Validators.required, Validators.min(0)]], + shortId: [DEFAULT_ID_SERVER, [Validators.required, Validators.min(1), Validators.max(65534), Validators.pattern('[0-9]*')]], + lifetime: [DEFAULT_LIFE_TIME, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], notifIfDisabled: [DEFAULT_NOTIF_IF_DESIBLED, []], - defaultMinPeriod: [DEFAULT_MIN_PERIOD, [Validators.required, Validators.min(0)]], + defaultMinPeriod: [DEFAULT_MIN_PERIOD, [Validators.required, Validators.min(0), Validators.pattern('[0-9]*')]], }), bootstrapServer: [null, Validators.required], lwm2mServer: [null, Validators.required] }), clientLwM2mSettings: this.fb.group({ - clientStrategy: [1, []], + clientOnlyObserveAfterConnect: [1, []], fwUpdateStrategy: [1, []], swUpdateStrategy: [1, []], fwUpdateRecourse: [{value: '', disabled: true}, []], @@ -177,12 +180,12 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } } - writeValue(value: Lwm2mProfileConfigModels | null): void { + async writeValue(value: Lwm2mProfileConfigModels | null) { if (isDefinedAndNotNull(value)) { - if (Object.keys(value).length !== 0 && (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap)) { + if (value?.clientLwM2mSettings || value?.observeAttr || value?.bootstrap) { this.configurationValue = value; } else { - this.configurationValue = getDefaultProfileConfig(); + this.configurationValue = await this.defaultProfileConfig(); } this.lwm2mDeviceConfigFormGroup.patchValue({ configurationJson: this.configurationValue @@ -191,6 +194,29 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } } + private async defaultProfileConfig(): Promise { + let bootstrap: ServerSecurityConfig; + let lwm2m: ServerSecurityConfig; + try { + [bootstrap, lwm2m] = await Promise.all([ + this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(true).toPromise(), + this.deviceProfileService.getLwm2mBootstrapSecurityInfoBySecurityType(false).toPromise() + ]); + } catch (e) { + bootstrap = getDefaultBootstrapServerSecurityConfig(); + lwm2m = getDefaultLwM2MServerSecurityConfig(); + } + return { + observeAttr: getDefaultProfileObserveAttrConfig(), + bootstrap: { + servers: getDefaultBootstrapServersSecurityConfig(), + bootstrapServer: bootstrap, + lwm2mServer: lwm2m + }, + clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig() + }; + } + private initWriteValue = (): void => { const modelValue = {objectIds: [], objectsList: []} as ModelValue; modelValue.objectIds = this.getObjectsFromJsonAllConfig(); @@ -220,7 +246,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value.objectsList), bootstrap: this.configurationValue.bootstrap, clientLwM2mSettings: { - clientStrategy: this.configurationValue.clientLwM2mSettings.clientStrategy, + clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, fwUpdateStrategy: this.configurationValue.clientLwM2mSettings.fwUpdateStrategy || 1, swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1, fwUpdateRecourse: fwResource, 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 a5d3502808..cbd981c4c8 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 @@ -39,7 +39,7 @@ export const DEFAULT_BOOTSTRAP_SERVER_ACCOUNT_TIME_OUT = 0; export const LEN_MAX_PUBLIC_KEY_RPK = 182; export const LEN_MAX_PUBLIC_KEY_X509 = 3000; export const KEY_REGEXP_HEX_DEC = /^[-+]?[0-9A-Fa-f]+\.?[0-9A-Fa-f]*?$/; -export const KEY_REGEXP_NUMBER = /^(\-?|\+?)\d*$/; +export const KEY_REGEXP_NUMBER = /^(-?|\+?)\d*$/; export const INSTANCES_ID_VALUE_MIN = 0; export const INSTANCES_ID_VALUE_MAX = 65535; export const DEFAULT_OTA_UPDATE_PROTOCOL = 'coap://'; @@ -143,18 +143,20 @@ export interface BootstrapServersSecurityConfig { export interface ServerSecurityConfig { host?: string; - securityHost?: string; port?: number; - securityPort?: number; securityMode: securityConfigMode; - clientPublicKeyOrId?: string; - clientSecretKey?: string; serverPublicKey?: string; clientHoldOffTime?: number; serverId?: number; bootstrapServerAccountTimeout: number; } +export interface ServerSecurityConfigInfo extends ServerSecurityConfig { + securityHost?: string; + securityPort?: number; + bootstrapServerIs: boolean; +} + interface BootstrapSecurityConfig { servers: BootstrapServersSecurityConfig; bootstrapServer: ServerSecurityConfig; @@ -168,7 +170,7 @@ export interface Lwm2mProfileConfigModels { } export interface ClientLwM2mSettings { - clientStrategy: string; + clientOnlyObserveAfterConnect: number; fwUpdateStrategy: number; swUpdateStrategy: number; fwUpdateRecourse: string; @@ -193,9 +195,9 @@ export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecu }; } -export function getDefaultBootstrapServerSecurityConfig(hostname: string): ServerSecurityConfig { +export function getDefaultBootstrapServerSecurityConfig(): ServerSecurityConfig { return { - host: hostname, + host: DEFAULT_LOCAL_HOST_NAME, port: DEFAULT_PORT_BOOTSTRAP_NO_SEC, securityMode: securityConfigMode.NO_SEC, serverPublicKey: '', @@ -205,22 +207,14 @@ export function getDefaultBootstrapServerSecurityConfig(hostname: string): Serve }; } -export function getDefaultLwM2MServerSecurityConfig(hostname): ServerSecurityConfig { - const DefaultLwM2MServerSecurityConfig = getDefaultBootstrapServerSecurityConfig(hostname); +export function getDefaultLwM2MServerSecurityConfig(): ServerSecurityConfig { + const DefaultLwM2MServerSecurityConfig = getDefaultBootstrapServerSecurityConfig(); DefaultLwM2MServerSecurityConfig.port = DEFAULT_PORT_SERVER_NO_SEC; DefaultLwM2MServerSecurityConfig.serverId = DEFAULT_ID_SERVER; return DefaultLwM2MServerSecurityConfig; } -function getDefaultProfileBootstrapSecurityConfig(hostname: any): BootstrapSecurityConfig { - return { - servers: getDefaultBootstrapServersSecurityConfig(), - bootstrapServer: getDefaultBootstrapServerSecurityConfig(hostname), - lwm2mServer: getDefaultLwM2MServerSecurityConfig(hostname) - }; -} - -function getDefaultProfileObserveAttrConfig(): ObservableAttributes { +export function getDefaultProfileObserveAttrConfig(): ObservableAttributes { return { observe: [], attribute: [], @@ -230,17 +224,9 @@ function getDefaultProfileObserveAttrConfig(): ObservableAttributes { }; } -export function getDefaultProfileConfig(hostname?: any): Lwm2mProfileConfigModels { - return { - clientLwM2mSettings: getDefaultProfileClientLwM2mSettingsConfig(), - observeAttr: getDefaultProfileObserveAttrConfig(), - bootstrap: getDefaultProfileBootstrapSecurityConfig((hostname) ? hostname : DEFAULT_LOCAL_HOST_NAME) - }; -} - -function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { +export function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSettings { return { - clientStrategy: '1', + clientOnlyObserveAfterConnect: 1, fwUpdateStrategy: 1, swUpdateStrategy: 1, fwUpdateRecourse: DEFAULT_FW_UPDATE_RESOURCE, 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 3537a13a77..075c656c26 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1257,10 +1257,14 @@ "servers": "Servers", "short-id": "Short ID", "short-id-required": "Short ID is required.", - "lifetime": "LwM2M Client registration Lifetime", - "lifetime-required": "LwM2M Client registration Lifetime is required.", - "default-min-period": "Minimum Period between two notifications (sec)", - "default-min-period-required": "Minimum Period is required.", + "short-id-range": "Short ID should be in a range from 1 to 65534.", + "short-id-pattern": "Short ID must be a positive integer.", + "lifetime": "Client registration lifetime", + "lifetime-required": "Client registration lifetime is required.", + "lifetime-pattern": "Client registration lifetime must be a positive integer.", + "default-min-period": "Minimum period between two notifications (s)", + "default-min-period-required": "Minimum period is required.", + "default-min-period-pattern": "Minimum period must be a positive integer.", "notification-storing": "Notification storing when disabled or offline", "binding": "Binding", "binding-type": { @@ -1282,15 +1286,19 @@ "server-host-required": "Host is required.", "server-port": "Port", "server-port-required": "Port is required.", + "server-port-pattern": "Port must be a positive integer.", + "server-port-range": "Port should be in a range from 1 to 65535.", "server-public-key": "Server Public Key", "server-public-key-required": "Server Public Key is required.", "server-public-key-pattern": "Server Public Key must be hex decimal format.", "server-public-key-length": "Server Public Key must be {{ count }} characters.", "client-hold-off-time": "Hold Off Time", "client-hold-off-time-required": "Hold Off Time is required.", + "client-hold-off-time-pattern": "Hold Off Time must be a positive integer.", "client-hold-off-time-tooltip": "Client Hold Off Time for use with a Bootstrap-Server only", "account-after-timeout": "Account after the timeout", "account-after-timeout-required": "Account after the timeout is required.", + "account-after-timeout-pattern": "Account after the timeout must be a positive integer.", "account-after-timeout-tooltip": "Bootstrap-Server Account after the timeout value given by this resource.", "others-tab": "Other settings", "client-strategy": "Client strategy when connecting", From ac18585e17f41f0dc473b5a8400770b26fb61f6b Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Tue, 22 Jun 2021 18:03:44 +0300 Subject: [PATCH 457/476] Coap: noSec one Endpoint --- .../server/coapserver/DefaultCoapServerService.java | 13 +------------ .../server/transport/coap/CoapTransportService.java | 11 ----------- 2 files changed, 1 insertion(+), 23 deletions(-) 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 605952e456..43d33c8a82 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 @@ -27,8 +27,6 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.net.InetAddress; -import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Random; import java.util.concurrent.ConcurrentMap; @@ -87,12 +85,6 @@ public class DefaultCoapServerService implements CoapServerService { } private CoapServer createCoapServer() throws UnknownHostException { - server = new CoapServer(); - - CoapEndpoint.Builder noSecCoapEndpointBuilder = new CoapEndpoint.Builder(); - InetAddress addr = InetAddress.getByName(coapServerContext.getHost()); - InetSocketAddress sockAddr = new InetSocketAddress(addr, coapServerContext.getPort()); - noSecCoapEndpointBuilder.setInetSocketAddress(sockAddr); NetworkConfig networkConfig = new NetworkConfig(); networkConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); networkConfig.setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); @@ -102,10 +94,7 @@ public class DefaultCoapServerService implements CoapServerService { networkConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); networkConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); networkConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); - noSecCoapEndpointBuilder.setNetworkConfig(networkConfig); - CoapEndpoint noSecCoapEndpoint = noSecCoapEndpointBuilder.build(); - server.addEndpoint(noSecCoapEndpoint); - + server = new CoapServer(networkConfig, coapServerContext.getPort()); if (isDtlsEnabled()) { CoapEndpoint.Builder dtlsCoapEndpointBuilder = new CoapEndpoint.Builder(); TbCoapDtlsSettings dtlsSettings = coapServerContext.getDtlsSettings(); 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 a5adc36d7e..93096c901e 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.eclipse.californium.core.network.config.NetworkConfig; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.server.coapserver.CoapServerService; @@ -31,8 +30,6 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.net.UnknownHostException; -import static org.eclipse.californium.core.network.config.NetworkConfigDefaults.DEFAULT_BLOCKWISE_STATUS_LIFETIME; - @Service("CoapTransportService") @TbCoapServerComponent @Slf4j @@ -55,14 +52,6 @@ public class CoapTransportService implements TbTransportService { public void init() throws UnknownHostException { log.info("Starting CoAP transport..."); coapServer = coapServerService.getCoapServer(); - coapServer.getConfig().setBoolean(NetworkConfig.Keys.BLOCKWISE_STRICT_BLOCK2_OPTION, true); - coapServer.getConfig().setBoolean(NetworkConfig.Keys.BLOCKWISE_ENTITY_TOO_LARGE_AUTO_FAILOVER, true); - coapServer.getConfig().setLong(NetworkConfig.Keys.BLOCKWISE_STATUS_LIFETIME, DEFAULT_BLOCKWISE_STATUS_LIFETIME); - coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_RESOURCE_BODY_SIZE, 256 * 1024 * 1024); - coapServer.getConfig().setString(NetworkConfig.Keys.RESPONSE_MATCHING, "RELAXED"); - coapServer.getConfig().setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); - coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); - coapServer.getConfig().setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 10); CoapResource api = new CoapResource(API); api.add(new CoapTransportResource(coapTransportContext, coapServerService, V1)); From daca44dbd871269cf6457cb93f1181bfb6014cfd Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 22 Jun 2021 18:08:02 +0300 Subject: [PATCH 458/476] revert TransportToDeviceActorMsg and added SendPendingRPCMsg to the end of the list. --- common/queue/src/main/proto/queue.proto | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index d47e6838d3..049b6934e7 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -443,12 +443,12 @@ message TransportToDeviceActorMsg { GetAttributeRequestMsg getAttributes = 3; SubscribeToAttributeUpdatesMsg subscribeToAttributes = 4; SubscribeToRPCMsg subscribeToRPC = 5; - SendPendingRPCMsg sendPendingRPC = 6; - ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 7; - SubscriptionInfoProto subscriptionInfo = 8; - ClaimDeviceMsg claimDevice = 9; - ProvisionDeviceRequestMsg provisionDevice = 10; - ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 11; + ToDeviceRpcResponseMsg toDeviceRPCCallResponse = 6; + SubscriptionInfoProto subscriptionInfo = 7; + ClaimDeviceMsg claimDevice = 8; + ProvisionDeviceRequestMsg provisionDevice = 9; + ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 10; + SendPendingRPCMsg sendPendingRPC = 11; } message TransportToRuleEngineMsg { From de4fa446f24b36141a4658ac5e3f1d0178c7ff68 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Tue, 22 Jun 2021 18:13:39 +0300 Subject: [PATCH 459/476] Correct class name --- .../{HasKey.java => AbstractLwM2MClientCredentialsWithKey.java} | 2 +- .../data/device/credentials/lwm2m/PSKClientCredentials.java | 2 +- .../data/device/credentials/lwm2m/RPKClientCredentials.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/{HasKey.java => AbstractLwM2MClientCredentialsWithKey.java} (91%) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentialsWithKey.java similarity index 91% rename from common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java rename to common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentialsWithKey.java index 58cfa84099..2a3c0ab434 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/HasKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/AbstractLwM2MClientCredentialsWithKey.java @@ -21,7 +21,7 @@ import lombok.Setter; import lombok.SneakyThrows; import org.apache.commons.codec.binary.Hex; -public abstract class HasKey extends AbstractLwM2MClientCredentials { +public abstract class AbstractLwM2MClientCredentialsWithKey extends AbstractLwM2MClientCredentials { @Getter @Setter private String key; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java index 2566af7da8..f90e85ff4a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/PSKClientCredentials.java @@ -20,7 +20,7 @@ import lombok.Setter; @Getter @Setter -public class PSKClientCredentials extends HasKey { +public class PSKClientCredentials extends AbstractLwM2MClientCredentialsWithKey { private String identity; @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java index fe329558f8..4ebe2de71a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/credentials/lwm2m/RPKClientCredentials.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data.device.credentials.lwm2m; -public class RPKClientCredentials extends HasKey { +public class RPKClientCredentials extends AbstractLwM2MClientCredentialsWithKey { @Override public LwM2MSecurityMode getSecurityConfigClientMode() { From d077ee6a07a29c2184ff1bbae3a3a5932bb6aa6a Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 22 Jun 2021 18:59:19 +0300 Subject: [PATCH 460/476] Improved Security Store to support race conditions during registration --- .../lwm2m/X509LwM2MIntegrationTest.java | 6 +-- .../TbLwM2MDtlsCertificateVerifier.java | 6 +-- .../server/client/LwM2mClientContextImpl.java | 10 ++-- .../server/store/TbLwM2mSecurityStore.java | 49 ++++++++++++++----- .../server/store/TbLwM2mStoreFactory.java | 2 +- .../server/store/TbMainSecurityStore.java | 29 +++++++++++ 6 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbMainSecurityStore.java diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java index 661f7c5474..7b60414e9c 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/X509LwM2MIntegrationTest.java @@ -75,13 +75,11 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { return device; } - //TODO: use different endpoints to isolate tests. - @Ignore() @Test public void testConnectAndObserveTelemetry() throws Exception { createDeviceProfile(TRANSPORT_CONFIGURATION); X509ClientCredentials credentials = new X509ClientCredentials(); - credentials.setEndpoint(endpoint+1); + credentials.setEndpoint(endpoint); Device device = createDevice(credentials); SingleEntityFilter sef = new SingleEntityFilter(); @@ -99,7 +97,7 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { wsClient.waitForReply(); wsClient.registerWaitForUpdate(); - LwM2MTestClient client = new LwM2MTestClient(executor, endpoint+1); + LwM2MTestClient client = new LwM2MTestClient(executor, endpoint); Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); client.init(security, coapConfig); String msg = wsClient.waitForUpdate(); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java index 792ba131e8..04b69c815f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java @@ -42,8 +42,8 @@ import org.thingsboard.server.common.transport.util.SslUtil; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.credentials.LwM2MCredentials; -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; import javax.annotation.PostConstruct; import javax.security.auth.x500.X500Principal; @@ -67,7 +67,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer private final TbLwM2MDtlsSessionStore sessionStorage; private final LwM2MTransportServerConfig config; private final LwM2mCredentialsSecurityInfoValidator securityInfoValidator; - private final TbEditableSecurityStore securityStore; + private final TbMainSecurityStore securityStore; @SuppressWarnings("deprecation") private StaticCertificateVerifier staticCertificateVerifier; @@ -134,7 +134,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer if (msg.hasDeviceInfo() && deviceProfile != null) { sessionStorage.put(endpoint, new TbX509DtlsSessionInfo(cert.getSubjectX500Principal().getName(), msg)); try { - securityStore.put(securityInfo); + securityStore.putX509(securityInfo); } catch (NonUniqueSecurityInfoException e) { log.trace("Failed to add security info: {}", securityInfo, e); } 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 d60482a0c1..700a7aec78 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.client; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.SecurityMode; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; @@ -30,7 +31,7 @@ import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; -import org.thingsboard.server.transport.lwm2m.server.store.TbEditableSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; import java.util.Arrays; import java.util.Collection; @@ -54,7 +55,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; - private final TbEditableSecurityStore securityStore; + private final TbMainSecurityStore securityStore; private final Map lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); private final Map lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); private final Map profiles = new ConcurrentHashMap<>(); @@ -75,6 +76,9 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { oldSession = lwM2MClient.getSession(); TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint()); if (securityInfo.getSecurityMode() != null) { + if (SecurityMode.X509.equals(securityInfo.getSecurityMode())) { + securityStore.registerX509(registration.getEndpoint(), registration.getId()); + } if (securityInfo.getDeviceProfile() != null) { profileUpdate(securityInfo.getDeviceProfile()); if (securityInfo.getSecurityInfo() != null) { @@ -124,7 +128,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { if (currentRegistration.getId().equals(registration.getId())) { lwM2MClient.setState(LwM2MClientState.UNREGISTERED); lwM2mClientsByEndpoint.remove(lwM2MClient.getEndpoint()); - this.securityStore.remove(lwM2MClient.getEndpoint()); + this.securityStore.remove(lwM2MClient.getEndpoint(), registration.getId()); UUID profileId = lwM2MClient.getProfileId(); if (profileId != null) { Optional otherClients = lwM2mClientsByRegistrationId.values().stream().filter(e -> e.getProfileId().equals(profileId)).findFirst(); 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 index d47be49978..bf1f275f32 100644 --- 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 @@ -22,13 +22,22 @@ import org.jetbrains.annotations.Nullable; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT; @Slf4j -public class TbLwM2mSecurityStore implements TbEditableSecurityStore { +public class TbLwM2mSecurityStore implements TbMainSecurityStore { private final TbEditableSecurityStore securityStore; private final LwM2mCredentialsSecurityInfoValidator validator; + private final ConcurrentMap> endpointRegistrations = new ConcurrentHashMap<>(); public TbLwM2mSecurityStore(TbEditableSecurityStore securityStore, LwM2mCredentialsSecurityInfoValidator validator) { this.securityStore = securityStore; @@ -61,24 +70,42 @@ public class TbLwM2mSecurityStore implements TbEditableSecurityStore { @Nullable public SecurityInfo fetchAndPutSecurityInfo(String credentialsId) { TbLwM2MSecurityInfo securityInfo = validator.getEndpointSecurityInfoByCredentialsId(credentialsId, CLIENT); - try { - if (securityInfo != null) { + doPut(securityInfo); + return securityInfo != null ? securityInfo.getSecurityInfo() : null; + } + + private void doPut(TbLwM2MSecurityInfo securityInfo) { + if (securityInfo != null) { + try { securityStore.put(securityInfo); + } catch (NonUniqueSecurityInfoException e) { + log.trace("Failed to add security info: {}", securityInfo, e); } - } catch (NonUniqueSecurityInfoException e) { - log.trace("Failed to add security info: {}", securityInfo, e); } - return securityInfo != null ? securityInfo.getSecurityInfo() : null; } @Override - public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { - securityStore.put(tbSecurityInfo); + public void putX509(TbLwM2MSecurityInfo securityInfo) throws NonUniqueSecurityInfoException { + securityStore.put(securityInfo); } @Override - public void remove(String endpoint) { - //TODO: Make sure we delay removal of security store from endpoint due to reg/unreg race condition. -// securityStore.remove(endpoint); + public void registerX509(String endpoint, String registrationId) { + endpointRegistrations.computeIfAbsent(endpoint, ep -> new HashSet<>()).add(registrationId); + } + + @Override + public void remove(String endpoint, String registrationId) { + Set epRegistrationIds = endpointRegistrations.get(endpoint); + boolean shouldRemove; + if (epRegistrationIds == null) { + shouldRemove = true; + } else { + epRegistrationIds.remove(registrationId); + shouldRemove = epRegistrationIds.isEmpty(); + } + if (shouldRemove) { + securityStore.remove(endpoint); + } } } 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 index 154de636de..b9eb865df5 100644 --- 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 @@ -51,7 +51,7 @@ public class TbLwM2mStoreFactory { } @Bean - private TbEditableSecurityStore securityStore() { + private TbMainSecurityStore securityStore() { return new TbLwM2mSecurityStore(redisConfiguration.isPresent() && useRedis ? new TbLwM2mRedisSecurityStore(redisConfiguration.get().redisConnectionFactory()) : new TbInMemorySecurityStore(), validator); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbMainSecurityStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbMainSecurityStore.java new file mode 100644 index 0000000000..f4394fb337 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbMainSecurityStore.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.transport.lwm2m.server.store; + +import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; +import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; + +public interface TbMainSecurityStore extends TbSecurityStore { + + void putX509(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException; + + void registerX509(String endpoint, String registrationId); + + void remove(String endpoint, String registrationId); + +} From 69271305d80dbf3e6feba33754367b621372e228 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Tue, 22 Jun 2021 19:39:46 +0300 Subject: [PATCH 461/476] Fix typo in rest method --- .../src/main/java/org/thingsboard/rest/client/RestClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2d0914c7d4..0c4b673d7f 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 @@ -2378,7 +2378,7 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { public void setUserCredentialsEnabled(UserId userId, boolean userCredentialsEnabled) { restTemplate.postForLocation( - baseURL + "/api/user/{userId}/userCredentialsEnabled?serCredentialsEnabled={serCredentialsEnabled}", + baseURL + "/api/user/{userId}/userCredentialsEnabled?userCredentialsEnabled={userCredentialsEnabled}", null, userId.getId(), userCredentialsEnabled); From 91863ac1c161140cc0489580d035012257327c73 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 23 Jun 2021 14:52:11 +0300 Subject: [PATCH 462/476] Coap: noSec one Endpoint/Sec one EndPoint host&port from yml --- .../coapserver/DefaultCoapServerService.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) 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 43d33c8a82..3eebbf0d3c 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 @@ -27,6 +27,8 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.util.Random; import java.util.concurrent.ConcurrentMap; @@ -94,11 +96,23 @@ public class DefaultCoapServerService implements CoapServerService { networkConfig.setInt(NetworkConfig.Keys.PREFERRED_BLOCK_SIZE, 1024); networkConfig.setInt(NetworkConfig.Keys.MAX_MESSAGE_SIZE, 1024); networkConfig.setInt(NetworkConfig.Keys.MAX_RETRANSMIT, 4); - server = new CoapServer(networkConfig, coapServerContext.getPort()); + networkConfig.setInt(NetworkConfig.Keys.COAP_PORT, coapServerContext.getPort()); + server = new CoapServer(networkConfig); + + CoapEndpoint.Builder noSecCoapEndpointBuilder = new CoapEndpoint.Builder(); + InetAddress addr = InetAddress.getByName(coapServerContext.getHost()); + InetSocketAddress sockAddr = new InetSocketAddress(addr, coapServerContext.getPort()); + noSecCoapEndpointBuilder.setInetSocketAddress(sockAddr); + + noSecCoapEndpointBuilder.setNetworkConfig(networkConfig); + CoapEndpoint noSecCoapEndpoint = noSecCoapEndpointBuilder.build(); + server.addEndpoint(noSecCoapEndpoint); if (isDtlsEnabled()) { CoapEndpoint.Builder dtlsCoapEndpointBuilder = new CoapEndpoint.Builder(); TbCoapDtlsSettings dtlsSettings = coapServerContext.getDtlsSettings(); DtlsConnectorConfig dtlsConnectorConfig = dtlsSettings.dtlsConnectorConfig(); + networkConfig.setInt(NetworkConfig.Keys.COAP_SECURE_PORT, dtlsConnectorConfig.getAddress().getPort()); + dtlsCoapEndpointBuilder.setNetworkConfig(networkConfig); DTLSConnector connector = new DTLSConnector(dtlsConnectorConfig); dtlsCoapEndpointBuilder.setConnector(connector); CoapEndpoint dtlsCoapEndpoint = dtlsCoapEndpointBuilder.build(); From 66d4c3fd20bf7843030820a44c06635156a109bc Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard <77969531+AndrewVolosytnykhThingsboard@users.noreply.github.com> Date: Wed, 23 Jun 2021 14:54:27 +0300 Subject: [PATCH 463/476] Clean exception message without class name and path (#4758) * Clean exception message without class name and path * Correct message, improvents to text of poppup on deleting * Added log for except files * Correct license header --- .../service/resource/DefaultTbResourceService.java | 11 ++++++----- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) 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 cd8aa88863..689b7fd9a9 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 @@ -86,7 +86,10 @@ public class DefaultTbResourceService implements TbResourceService { } else { throw new DataValidationException(String.format("Could not parse the XML of objectModel with name %s", resource.getSearchText())); } - } catch (InvalidDDFFileException | IOException e) { + } catch (InvalidDDFFileException e) { + log.error("Failed to parse file {}", resource.getFileName(), e); + throw new DataValidationException("Failed to parse file " + resource.getFileName()); + } catch (IOException e) { throw new ThingsboardException(e, ThingsboardErrorCode.GENERAL); } if (resource.getResourceType().equals(ResourceType.LWM2M_MODEL) && toLwM2mObject(resource, true) == null) { @@ -194,8 +197,7 @@ public class DefaultTbResourceService implements TbResourceService { if (isSave) { LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); resources.add(lwM2MResourceObserve); - } - else if (v.operations.isReadable()) { + } else if (v.operations.isReadable()) { LwM2mResourceObserve lwM2MResourceObserve = new LwM2mResourceObserve(k, v.name, false, false, false); resources.add(lwM2MResourceObserve); } @@ -204,8 +206,7 @@ public class DefaultTbResourceService implements TbResourceService { instance.setResources(resources.toArray(LwM2mResourceObserve[]::new)); lwM2mObject.setInstances(new LwM2mInstance[]{instance}); return lwM2mObject; - } - else { + } else { return null; } } 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 4af8c7a87a..6736c168b2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2362,7 +2362,7 @@ "delete-resource-text": "Be careful, after the confirmation the resource will become unrecoverable.", "delete-resource-title": "Are you sure you want to delete the resource '{{resourceTitle}}'?", "delete-resources-action-title": "Delete { count, plural, 1 {1 resource} other {# resources} }", - "delete-resources-text": "Be careful, after the confirmation all selected resources will be removed.", + "delete-resources-text": "Please note that the selected resources, even if they are used in device profiles, will be deleted.", "delete-resources-title": "Are you sure you want to delete { count, plural, 1 {1 resource} other {# resources} }?", "download": "Download resource", "drop-file": "Drop a resource file or click to select a file to upload.", From c9b83a4c88bfaafa3943232bd72266008c37767d Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 23 Jun 2021 19:22:14 +0300 Subject: [PATCH 464/476] UI: Refactoring LWM2M device profile transport configuration attributes settings --- .../device/data/lwm2m/ObjectAttributes.java | 10 +- .../lwm2m-attributes-dialog.component.html | 15 +- .../lwm2m-attributes-dialog.component.ts | 48 ++-- .../lwm2m-attributes-key-list.component.html | 83 +++---- .../lwm2m-attributes-key-list.component.scss | 69 ++++++ .../lwm2m-attributes-key-list.component.ts | 224 +++++++++--------- .../lwm2m/lwm2m-attributes.component.html | 10 +- .../lwm2m/lwm2m-attributes.component.scss | 52 +--- .../lwm2m/lwm2m-attributes.component.ts | 80 +++---- ...rve-attr-telemetry-resource.component.html | 22 +- ...wm2m-observe-attr-telemetry.component.html | 13 +- .../lwm2m/lwm2m-profile-config.models.ts | 72 +++--- .../assets/locale/locale.constant-en_US.json | 38 +-- 13 files changed, 381 insertions(+), 355 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.scss diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java index ee54a23a07..7236774bcc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java @@ -24,10 +24,10 @@ public class ObjectAttributes { private Long dim; private String ver; - private Long pmin; - private Long pmax; - private Double gt; - private Double lt; - private Double st; + private Integer pmin; + private Integer pmax; + private Float gt; + private Float lt; + private Float st; } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html index 5599e8d116..fbedbb5f76 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-dialog.component.html @@ -15,12 +15,11 @@ limitations under the License. --> -
+ -
-

{{ (readonly ? 'device-profile.lwm2m.attribute-lwm2m-toolbar-view' : - 'device-profile.lwm2m.attribute-lwm2m-toolbar-edit') | translate }}

-
+

+ {{ (readonly ? 'device-profile.lwm2m.view-attributes' : 'device-profile.lwm2m.edit-attributes') | translate : {name: name} }} +

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 c1877e218e..6c674febf5 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,7 +14,7 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { Component, Inject, SkipSelf } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { DialogComponent } from '@shared/components/dialog.component'; import { Store } from '@ngrx/store'; @@ -22,12 +22,13 @@ 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'; +import { AttributesNameValueMap } from '@home/components/profile/device/lwm2m/lwm2m-profile-config.models'; export interface Lwm2mAttributesDialogData { readonly: boolean; - attributeLwm2m: JsonObject; - destName: string; + attributes: AttributesNameValueMap; + modelName: string; + isResource: boolean; } @Component({ @@ -36,42 +37,37 @@ 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 ErrorStateMatcher { - readonly = this.data.readonly; - - attributeLwm2m = this.data.attributeLwm2m; - - submitted = false; + readonly: boolean; + name: string; + isResource: boolean; - dirtyValue = false; + private submitted = false; - attributeLwm2mDialogFormGroup: FormGroup; + attributeFormGroup: FormGroup; constructor(protected store: Store, protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: Lwm2mAttributesDialogData, + @Inject(MAT_DIALOG_DATA) private data: Lwm2mAttributesDialogData, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, private fb: FormBuilder) { super(store, router, dialogRef); - this.attributeLwm2mDialogFormGroup = this.fb.group({ - keyFilters: [{}, []] - }); - this.attributeLwm2mDialogFormGroup.patchValue({keyFilters: this.attributeLwm2m}); - this.attributeLwm2mDialogFormGroup.get('keyFilters').valueChanges.subscribe((attributes) => { - this.attributeLwm2m = attributes; + this.readonly = data.readonly; + this.name = data.modelName; + this.isResource = data.isResource; + + this.attributeFormGroup = this.fb.group({ + attributes: [data.attributes] }); if (this.readonly) { - this.attributeLwm2mDialogFormGroup.disable({emitEvent: false}); + this.attributeFormGroup.disable({emitEvent: false}); } } - ngOnInit(): void { - } - isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { const originalErrorState = this.errorStateMatcher.isErrorState(control, form); const customErrorState = !!(control && control.invalid && this.submitted); @@ -80,7 +76,7 @@ export class Lwm2mAttributesDialogComponent extends DialogComponent -
-
- device-profile.lwm2m.attribute-lwm2m-destination - {{ titleText }} -
+
- device-profile.lwm2m.attribute-lwm2m-name - device-profile.lwm2m.attribute-lwm2m-value -
+ + +
-
-
- +
+
+ - - - {{ attributeLwm2mMap.get(attrKey[attributeLwm2m]) }} + + + {{ attributeNameTranslationMap.get(attributeName) | translate }} + + {{ 'device-profile.lwm2m.attribute-name-required' | translate }} + - + - + + + {{ 'device-profile.lwm2m.attribute-value-required' | translate }} + + + {{ 'device-profile.lwm2m.attribute-value-pattern' | translate }} + -
- - {{ 'device-profile.lwm2m.key-name' | translate }} - {{ 'device-profile.lwm2m.required' | translate }} - - - {{ 'device-profile.lwm2m.valid-attribute-lwm2m-key' | translate: {attrEnums: attrKeys} }} - - - {{ 'device-profile.lwm2m.valid-attribute-lwm2m-value' | translate: {attrEnums: attrKeys} }} -
- {{noDataText ? noDataText : 'device-profile.lwm2m.no-data'}} -
-
+
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.scss new file mode 100644 index 0000000000..23515e87bc --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes-key-list.component.scss @@ -0,0 +1,69 @@ +/** + * 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 { + .name-value-map { + span.no-data-found { + position: relative; + display: flex; + height: 40px; + + &.disabled { + color: rgba(0, 0, 0, .38); + } + } + + .map-list{ + height: 45px; + } + } +} + +:host ::ng-deep { + .mat-form-field-wrapper { + padding-bottom: 0; + } + .mat-form-field-infix { + border-top: 0; + } + .mat-form-field-underline { + bottom: 0; + } + + .button-icon{ + font-size: 20px; + width: 20px; + height: 20px; + } + + .map-list { + mat-form-field { + .mat-form-field-wrapper { + padding-bottom: 0; + .mat-form-field-infix { + border-top-width: 0.2em; + width: auto; + min-width: auto; + } + .mat-form-field-underline { + bottom: 0; + } + .mat-form-field-subscript-wrapper{ + margin-top: 1.8em; + } + } + } + } +} 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 686382e9bd..f84a0511c6 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,35 +14,36 @@ /// limitations under the License. /// -import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { AbstractControl, ControlValueAccessor, FormArray, FormBuilder, - FormControl, FormGroup, NG_VALIDATORS, NG_VALUE_ACCESSOR, Validator, Validators } from '@angular/forms'; -import { Subscription } from 'rxjs'; +import { Subject, Subscription } from 'rxjs'; import { - ATTRIBUTE_KEYS, - ATTRIBUTE_LWM2M_ENUM, - ATTRIBUTE_LWM2M_MAP + AttributeName, + AttributeNameTranslationMap, + AttributesNameValue, + AttributesNameValueMap, + valueValidatorByAttributeName } from './lwm2m-profile-config.models'; -import { isDefinedAndNotNull, isEmpty, isEmptyStr, isUndefinedOrNull } from '@core/utils'; +import { isUndefinedOrNull } from '@core/utils'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; - +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'tb-lwm2m-attributes-key-list', templateUrl: './lwm2m-attributes-key-list.component.html', - styleUrls: ['./lwm2m-attributes.component.scss'], + styleUrls: ['./lwm2m-attributes-key-list.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, @@ -56,39 +57,46 @@ import { PageComponent } from '@shared/components/page.component'; } ] }) -export class Lwm2mAttributesKeyListComponent extends PageComponent implements ControlValueAccessor, OnInit, Validator { - - attrKeys = ATTRIBUTE_KEYS; - - attrKey = ATTRIBUTE_LWM2M_ENUM; +export class Lwm2mAttributesKeyListComponent extends PageComponent implements ControlValueAccessor, OnDestroy, OnDestroy, Validator { - attributeLwm2mMap = ATTRIBUTE_LWM2M_MAP; + attributeNames; + attributeNameTranslationMap = AttributeNameTranslationMap; @Input() disabled: boolean; - @Input() titleText: string; + @Input() + isResource = false; - @Input() noDataText: string; - - kvListFormGroup: FormGroup; + attributesValueFormGroup: FormGroup; private propagateChange = null; - - private valueChangeSubscription: Subscription = null; + private valueChange$: Subscription = null; + private destroy$ = new Subject(); + private usedAttributesName: AttributeName[] = []; constructor(protected store: Store, private fb: FormBuilder) { super(store); + this.attributesValueFormGroup = this.fb.group({ + attributesValue: this.fb.array([]) + }); } - ngOnInit(): void { - this.kvListFormGroup = this.fb.group({}); - this.kvListFormGroup.addControl('keyVals', - this.fb.array([])); + ngOnInit() { + if (this.isResource) { + this.attributeNames = Object.values(AttributeName); + } else { + this.attributeNames = Object.values(AttributeName) + .filter(item => ![AttributeName.lt, AttributeName.gt, AttributeName.st].includes(item)); + } } - keyValsFormArray(): FormArray { - return this.kvListFormGroup.get('keyVals') as FormArray; + ngOnDestroy() { + if (this.valueChange$) { + this.valueChange$.unsubscribe(); + } + this.destroy$.next(); + this.destroy$.complete(); } registerOnChange(fn: any): void { @@ -101,127 +109,111 @@ export class Lwm2mAttributesKeyListComponent extends PageComponent implements Co setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; if (this.disabled) { - this.kvListFormGroup.disable({emitEvent: false}); + this.attributesValueFormGroup.disable({emitEvent: false}); } else { - this.kvListFormGroup.enable({emitEvent: false}); + this.attributesValueFormGroup.enable({emitEvent: false}); } } - writeValue(keyValMap: { [key: string]: string }): void { - if (this.valueChangeSubscription) { - this.valueChangeSubscription.unsubscribe(); + writeValue(keyValMap: AttributesNameValueMap): void { + if (this.valueChange$) { + this.valueChange$.unsubscribe(); } - const keyValsControls: Array = []; + const attributesValueControls: Array = []; if (keyValMap) { - for (const property of Object.keys(keyValMap)) { - if (Object.prototype.hasOwnProperty.call(keyValMap, property)) { - keyValsControls.push(this.fb.group({ - key: [property, [Validators.required, this.attributeLwm2mKeyValidator]], - value: [keyValMap[property], this.attributeLwm2mValueValidator(property)] - })); - } - } + (Object.keys(keyValMap) as AttributeName[]).forEach(name => { + attributesValueControls.push(this.createdFormGroup({name, value: keyValMap[name]})); + }); } - this.kvListFormGroup.setControl('keyVals', this.fb.array(keyValsControls)); - this.valueChangeSubscription = this.kvListFormGroup.valueChanges.subscribe(() => { - // this.updateValidate(); - this.updateModel(); - }); + this.attributesValueFormGroup.setControl('attributesValue', this.fb.array(attributesValueControls)); if (this.disabled) { - this.kvListFormGroup.disable({emitEvent: false}); + this.attributesValueFormGroup.disable({emitEvent: false}); } else { - this.kvListFormGroup.enable({emitEvent: false}); + this.attributesValueFormGroup.enable({emitEvent: false}); } + this.valueChange$ = this.attributesValueFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + this.updateUsedAttributesName(); + } + + attributesValueFormArray(): FormArray { + return this.attributesValueFormGroup.get('attributesValue') as FormArray; } public removeKeyVal(index: number) { - (this.kvListFormGroup.get('keyVals') as FormArray).removeAt(index); + this.attributesValueFormArray().removeAt(index); } public addKeyVal() { - const keyValsFormArray = this.kvListFormGroup.get('keyVals') as FormArray; - keyValsFormArray.push(this.fb.group({ - key: ['', [Validators.required, this.attributeLwm2mKeyValidator]], - value: ['', []] - })); - } - - public validate(c?: FormControl) { - const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; - let valid = true; - for (const entry of kvList) { - if (isUndefinedOrNull(entry.key) || isEmptyStr(entry.key) || !ATTRIBUTE_KEYS.includes(entry.key)) { - valid = false; - break; - } - if (entry.key !== 'ver' && isNaN(Number(entry.value))) { - valid = false; - break; - } + this.attributesValueFormArray().push(this.createdFormGroup()); + this.attributesValueFormGroup.updateValueAndValidity({emitEvent: false}); + if (this.attributesValueFormGroup.invalid) { + this.updateModel(); } - return (valid) ? null : { - keyVals: { - valid: false, - }, - }; } - private updateValidate() { - const kvList = this.kvListFormGroup.get('keyVals') as FormArray; - kvList.controls.forEach(fg => { - if (fg.get('key').value === 'ver') { - fg.get('value').setValidators(null); - fg.get('value').setErrors(null); - } - else { - fg.get('value').setValidators(this.attributeLwm2mValueNumberValidator); - fg.get('value').setErrors(this.attributeLwm2mValueNumberValidator(fg.get('value'))); - } + private createdFormGroup(value?: AttributesNameValue): FormGroup { + if (isUndefinedOrNull(value)) { + value = { + name: this.getFirstUnusedAttributesName(), + value: null + }; + } + const form = this.fb.group({ + name: [value.name, Validators.required], + value: [value.value, valueValidatorByAttributeName(value.name)] }); + form.get('name').valueChanges.pipe( + takeUntil(this.destroy$) + ).subscribe(name => { + form.get('value').setValidators(valueValidatorByAttributeName(name)); + form.get('value').updateValueAndValidity(); + }); + return form; + } + + public validate() { + return this.attributesValueFormGroup.valid ? null : { + attributesValue: { + valid: false + } + }; } private updateModel() { - this.updateValidate(); - if (this.validate() === null) { - const kvList: { key: string; value: string }[] = this.kvListFormGroup.get('keyVals').value; - const keyValMap: { [key: string]: string | number } = {}; - kvList.forEach((entry) => { - if (isUndefinedOrNull(entry.value) || entry.key === 'ver' || isEmptyStr(entry.value.toString())) { - keyValMap[entry.key] = entry.value.toString(); - } else { - keyValMap[entry.key] = Number(entry.value); - } - }); - this.propagateChange(keyValMap); - } - else { - this.propagateChange(null); - } + const value: AttributesNameValue[] = this.attributesValueFormGroup.get('attributesValue').value; + const attributesNameValueMap: AttributesNameValueMap = {}; + value.forEach(attribute => { + attributesNameValueMap[attribute.name] = attribute.value; + }); + this.updateUsedAttributesName(); + this.propagateChange(attributesNameValueMap); } + public isDisabledAttributeName(type: AttributeName, index: number): boolean { + const usedIndex = this.usedAttributesName.indexOf(type); + return usedIndex > -1 && usedIndex !== index; + } - private attributeLwm2mKeyValidator = (control: AbstractControl) => { - const key = control.value as string; - if (isDefinedAndNotNull(key) && !isEmpty(key)) { - if (!ATTRIBUTE_KEYS.includes(key)) { - return { - validAttributeKey: true - }; + private getFirstUnusedAttributesName(): AttributeName { + for (const attributeName of this.attributeNames) { + if (this.usedAttributesName.indexOf(attributeName) === -1) { + return attributeName; } } return null; } - private attributeLwm2mValueNumberValidator = (control: AbstractControl) => { - if (isNaN(Number(control.value)) || Number(control.value) < 0) { - return { - validAttributeValue: true - }; - } - return null; + private updateUsedAttributesName() { + this.usedAttributesName = []; + const value: AttributesNameValue[] = this.attributesValueFormGroup.get('attributesValue').value; + value.forEach((attributesValue, index) => { + this.usedAttributesName[index] = attributesValue.name; + }); } - private attributeLwm2mValueValidator = (property: string): object[] => { - return property === 'ver' ? [] : [this.attributeLwm2mValueNumberValidator]; + get isAddEnabled(): boolean { + return this.attributesValueFormArray().length !== this.attributeNames.length; } } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.html index 2011cd65f5..b9e8c13ac6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.html @@ -15,18 +15,14 @@ limitations under the License. --> -
-
- {{attributeLwm2mToString()}} -
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.scss index 63dd0bdb3e..b33df98d85 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-attributes.component.scss @@ -14,48 +14,20 @@ * limitations under the License. */ :host { - .tb-kv-map { - span.no-data-found { - position: relative; - display: flex; - height: 40px; - - &.disabled { - color: rgba(0, 0, 0, .38); - } - } + .resource-name-lw-end{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align:end; + //width: 80px; + cursor: pointer; } -} -:host ::ng-deep { - .mat-form-field-wrapper { - padding-bottom: 0; - } - .mat-form-field-infix { - border-top: 0; + .resource-name-lw{ + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; } - .mat-form-field-underline { - bottom: 0; - } -} - -.vertical-padding { - padding: 0 0 10px 20px; -} - -.resource-name-lw-end{ - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - text-align:end; - //width: 80px; - cursor: pointer; -} - -.resource-name-lw{ - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: pointer; } 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 1e467bcd93..99d331d8a8 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 @@ -17,11 +17,10 @@ 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 { isEmpty, isUndefinedOrNull } 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'; +import { AttributesNameValueMap } from './lwm2m-profile-config.models'; @Component({ @@ -36,22 +35,21 @@ import { ATTRIBUTE_LWM2M_LABEL } from './lwm2m-profile-config.models'; }) export class Lwm2mAttributesComponent implements ControlValueAccessor { attributeLwm2mFormGroup: FormGroup; - attributeLwm2mLabel = ATTRIBUTE_LWM2M_LABEL; private requiredValue: boolean; - @Input() - attributeLwm2m: {}; - @Input() isAttributeTelemetry: boolean; @Input() - destName: string; + modelName: string; @Input() disabled: boolean; + @Input() + isResource = false; + @Output() updateAttributeLwm2m = new EventEmitter(); @@ -64,8 +62,7 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor { } constructor(private dialog: MatDialog, - private fb: FormBuilder, - private translate: TranslateService) {} + private fb: FormBuilder) {} registerOnChange(fn: any): void { this.propagateChange = fn; @@ -85,63 +82,66 @@ export class Lwm2mAttributesComponent implements ControlValueAccessor { ngOnInit() { this.attributeLwm2mFormGroup = this.fb.group({ - attributeLwm2m: [this.attributeLwm2m] + attributes: [{}] }); } - writeValue(value: {} | null): void {} - - attributeLwm2mToString = (): string => { - return this.isIconEditAdd () ? this.attributeLwm2mLabelToString() : this.translate.instant('device-profile.lwm2m.no-data'); + writeValue(value: AttributesNameValueMap | null) { + this.attributeLwm2mFormGroup.patchValue({attributes: value}, {emitEvent: false}); } - private attributeLwm2mLabelToString = (): string => { - let label = JSON.stringify(this.attributeLwm2m); - label = deepClone(label.replace('{', '')); - label = deepClone(label.replace('}', '')); - this.attributeLwm2mLabel.forEach((value: string, key: string) => { - const dest = '\"' + key + '\"\:'; - label = deepClone(label.replace(dest, value)); - }); - return label; + get attributesValueMap(): AttributesNameValueMap { + return this.attributeLwm2mFormGroup.get('attributes').value; } isDisableBtn(): boolean { - return this.disabled || this.isAttributeTelemetry ? !(isDefinedAndNotNull(this.attributeLwm2m) && - !isEmpty(this.attributeLwm2m) && this.disabled) : this.disabled; + return !this.disabled && this.isAttributeTelemetry; } - isIconView(): boolean { - return this.isAttributeTelemetry || this.disabled; + isEmpty(): boolean { + const value = this.attributesValueMap; + return isUndefinedOrNull(value) || isEmpty(value); } - isIconEditAdd(): boolean { - return isDefinedAndNotNull(this.attributeLwm2m) && !isEmpty(this.attributeLwm2m); + get tooltipSetAttributesTelemetry(): string { + return this.isDisableBtn() ? 'device-profile.lwm2m.edit-attributes-select' : ''; } - 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'); + get tooltipButton(): string { + if (this.disabled) { + return 'device-profile.lwm2m.view-attribute'; + } else if (this.isEmpty()) { + return 'device-profile.lwm2m.add-attribute'; + } + return 'device-profile.lwm2m.edit-attribute'; + } + + get iconButton(): string { + if (this.disabled) { + return 'visibility'; + } else if (this.isEmpty()) { + return 'add'; + } + return 'edit'; } public editAttributesLwm2m = ($event: Event): void => { if ($event) { $event.stopPropagation(); } - this.dialog.open(Lwm2mAttributesDialogComponent, { + this.dialog.open(Lwm2mAttributesDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { readonly: this.disabled, - attributeLwm2m: this.disabled ? this.attributeLwm2m : deepClone(this.attributeLwm2m), - destName: this.destName + attributes: this.attributesValueMap, + modelName: this.modelName, + isResource: this.isResource } }).afterClosed().subscribe((result) => { if (result) { - this.attributeLwm2m = result; - this.attributeLwm2mFormGroup.patchValue({attributeLwm2m: this.attributeLwm2m}); - this.updateAttributeLwm2m.next(this.attributeLwm2m); + this.attributeLwm2mFormGroup.patchValue({attributeLwm2m: result}); + this.updateAttributeLwm2m.next(result); } }); } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html index 3ee9bffa27..8c6a8fab84 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resource.component.html @@ -20,27 +20,24 @@ *ngFor="let resourceLwM2M of resourceFormArray.controls; let i = index; trackBy: trackByParams">
-
+
device-profile.lwm2m.resource-label
-
+
device-profile.lwm2m.attribute-label
-
+
device-profile.lwm2m.telemetry-label
-
+
device-profile.lwm2m.observe-label
device-profile.lwm2m.key-name-label
-
- device-profile.lwm2m.attribute-lwm2m-label -
-
<{{resourceLwM2M.get('id').value}}> {{resourceLwM2M.get('name').value}}
@@ -65,7 +62,7 @@ matTooltipPosition="above">
- + {{ 'device-profile.lwm2m.key-name-label' | translate }} {{ 'device-profile.lwm2m.required' | translate }} -
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html index 02d594905d..ee60f144ca 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.html @@ -26,17 +26,15 @@
- +