From 6ab586b0b13c48f0f41b279656f9e90bdb5d7d35 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Fri, 16 Apr 2021 15:49:37 +0300 Subject: [PATCH 001/231] Merging feature/html-emails from CE --- .../service/mail/DefaultMailService.java | 47 +++++++++++++------ .../rule/engine/api/MailService.java | 8 ++-- .../rule/engine/mail/EmailPojo.java | 4 ++ .../rule/engine/mail/TbMsgToEmailNode.java | 17 ++++++- .../mail/TbMsgToEmailNodeConfiguration.java | 2 + .../rule/engine/mail/TbSendEmailNode.java | 22 +++++++-- 6 files changed, 76 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index 972c99f866..ef4aa05bbd 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 @@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.MessageSource; import org.springframework.context.annotation.Lazy; import org.springframework.core.NestedRuntimeException; +import org.springframework.core.io.InputStreamSource; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; @@ -44,8 +45,8 @@ import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; -import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; +import java.io.ByteArrayInputStream; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -233,22 +234,38 @@ 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, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images) throws ThingsboardException { if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { - MimeMessage mailMsg = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); - helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); - helper.setTo(to.split("\\s*,\\s*")); - if (!StringUtils.isBlank(cc)) { - helper.setCc(cc.split("\\s*,\\s*")); + try { + MimeMessage mailMsg = mailSender.createMimeMessage(); + boolean multipart = (images != null && !images.isEmpty()); + MimeMessageHelper helper = new MimeMessageHelper(mailMsg, multipart, "UTF-8"); + helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); + helper.setTo(to.split("\\s*,\\s*")); + if (!StringUtils.isBlank(cc)) { + helper.setCc(cc.split("\\s*,\\s*")); + } + if (!StringUtils.isBlank(bcc)) { + helper.setBcc(bcc.split("\\s*,\\s*")); + } + helper.setSubject(subject); + helper.setText(body, isHtml); + + if (images != null && images.size() > 0) { + for (String imgId : images.keySet()) { + String imgValue = images.get(imgId); + String value = imgValue.replaceFirst("^data:image/[^;]*;base64,?", ""); + byte[] bytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(value); + String contentType = helper.getFileTypeMap().getContentType(imgId); + InputStreamSource iss = () -> new ByteArrayInputStream(bytes); + helper.addInline(imgId, iss, contentType); + } + } + mailSender.send(helper.getMimeMessage()); + apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); + } catch (Exception e) { + throw handleException(e); } - if (!StringUtils.isBlank(bcc)) { - helper.setBcc(bcc.split("\\s*,\\s*")); - } - helper.setSubject(subject); - helper.setText(body); - mailSender.send(helper.getMimeMessage()); - apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } else { throw new RuntimeException("Email sending is disabled due to API limits!"); } 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..e125927af4 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 @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; -import javax.mail.MessagingException; +import java.util.Map; public interface MailService { @@ -37,12 +37,12 @@ public interface MailService { void sendAccountActivatedEmail(String loginLink, String email) throws ThingsboardException; void sendResetPasswordEmail(String passwordResetLink, String email) throws ThingsboardException; - - 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 sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; + void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images) throws ThingsboardException; + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/EmailPojo.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/EmailPojo.java index cce9ab0b3e..5d43b47c3b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/EmailPojo.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/EmailPojo.java @@ -18,6 +18,8 @@ package org.thingsboard.rule.engine.mail; import lombok.Builder; import lombok.Data; +import java.util.Map; + @Data @Builder class EmailPojo { @@ -28,5 +30,7 @@ class EmailPojo { private final String bcc; private final String subject; private final String body; + private final Map images; + private final boolean html; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index c8415e3181..c9da3cc81a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.mail; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.util.StringUtils; @@ -27,9 +28,11 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgMetaData; import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; @@ -50,6 +53,12 @@ public class TbMsgToEmailNode implements TbNode { private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final String ATTACHMENTS = "attachments"; + private static final String IMAGES = "images"; + private static final String EMAIL_TIMEZONE = "emailTimezone"; + + private static final Pattern dateVarPattern = Pattern.compile("%d\\{([^\\}]*)\\}"); + private TbMsgToEmailNodeConfiguration config; @Override @@ -80,8 +89,14 @@ public class TbMsgToEmailNode implements TbNode { builder.to(fromTemplate(this.config.getToTemplate(), msg)); builder.cc(fromTemplate(this.config.getCcTemplate(), msg)); builder.bcc(fromTemplate(this.config.getBccTemplate(), msg)); + builder.html(Boolean.parseBoolean(fromTemplate(this.config.getIsHtmlTemplate(), msg))); builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg)); builder.body(fromTemplate(this.config.getBodyTemplate(), msg)); + String imagesStr = msg.getMetaData().getValue(IMAGES); + if (!StringUtils.isEmpty(imagesStr)) { + Map imgMap = MAPPER.readValue(imagesStr, new TypeReference>() {}); + builder.images(imgMap); + } return builder.build(); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java index a509acd39e..0ac1a379ec 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java @@ -27,6 +27,7 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { private String bccTemplate; private String subjectTemplate; private String bodyTemplate; + private String isHtmlTemplate; @Override public TbMsgToEmailNodeConfiguration defaultConfiguration() { @@ -35,6 +36,7 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { configuration.toTemplate = "${userEmail}"; configuration.subjectTemplate = "Device ${deviceType} temperature high"; configuration.bodyTemplate = "Device ${deviceName} has high temperature ${temp}"; + configuration.isHtmlTemplate = "${isHtml}"; return configuration; } } 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..543e68e1b3 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 @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.mail; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.core.io.InputStreamSource; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import org.thingsboard.rule.engine.api.RuleNode; @@ -26,12 +27,13 @@ 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; import javax.mail.internet.MimeMessage; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.Map; import java.util.Properties; import static org.thingsboard.common.util.DonAsynchron.withCallback; @@ -89,10 +91,11 @@ public class TbSendEmailNode implements TbNode { private void sendEmail(TbContext ctx, EmailPojo email) throws Exception { if (this.config.isUseSystemSmtpSettings()) { ctx.getMailService().send(ctx.getTenantId(), email.getFrom(), email.getTo(), email.getCc(), - email.getBcc(), email.getSubject(), email.getBody()); + email.getBcc(), email.getSubject(), email.getBody(), email.isHtml(), email.getImages()); } else { MimeMessage mailMsg = mailSender.createMimeMessage(); - MimeMessageHelper helper = new MimeMessageHelper(mailMsg, "UTF-8"); + boolean multipart = (email.getImages() != null && !email.getImages().isEmpty()); + MimeMessageHelper helper = new MimeMessageHelper(mailMsg, multipart, "UTF-8"); helper.setFrom(email.getFrom()); helper.setTo(email.getTo().split("\\s*,\\s*")); if (!StringUtils.isBlank(email.getCc())) { @@ -102,7 +105,18 @@ public class TbSendEmailNode implements TbNode { helper.setBcc(email.getBcc().split("\\s*,\\s*")); } helper.setSubject(email.getSubject()); - helper.setText(email.getBody()); + helper.setText(email.getBody(), email.isHtml()); + if (email.getImages() != null && email.getImages().size() > 0) { + Map images = email.getImages(); + for (String imgId : images.keySet()) { + String imgValue = images.get(imgId); + String value = imgValue.replaceFirst("^data:image/[^;]*;base64,?", ""); + byte[] bytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(value); + String contentType = helper.getFileTypeMap().getContentType(imgId); + InputStreamSource iss = () -> new ByteArrayInputStream(bytes); + helper.addInline(imgId, iss, contentType); + } + } mailSender.send(helper.getMimeMessage()); } } From 366ac8768be4afebb8c2ed1006d8b5a10b72a742 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Tue, 20 Apr 2021 18:19:56 +0300 Subject: [PATCH 002/231] Code cleaning, added ability to choose type of email body --- .../service/mail/DefaultMailService.java | 16 ++++++++-- .../rule/engine/api/MailService.java | 3 ++ .../rule/engine/mail/TbMsgToEmailNode.java | 18 ++++++----- .../mail/TbMsgToEmailNodeConfiguration.java | 2 ++ .../rule/engine/mail/TbSendEmailNode.java | 32 ++----------------- 5 files changed, 30 insertions(+), 41 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index ef4aa05bbd..755a397611 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 @@ -25,6 +25,7 @@ import org.springframework.context.MessageSource; import org.springframework.context.annotation.Lazy; import org.springframework.core.NestedRuntimeException; import org.springframework.core.io.InputStreamSource; +import org.springframework.mail.javamail.JavaMailSender; import org.springframework.mail.javamail.JavaMailSenderImpl; import org.springframework.mail.javamail.MimeMessageHelper; import org.springframework.stereotype.Service; @@ -235,9 +236,18 @@ public class DefaultMailService implements MailService { @Override public void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images) throws ThingsboardException { + sendMail(tenantId, from, to, cc, bcc, subject, body, isHtml, images, this.mailSender); + } + + @Override + public void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images, JavaMailSender javaMailSender) throws ThingsboardException { + sendMail(tenantId, from, to, cc, bcc, subject, body, isHtml, images, javaMailSender); + } + + private void sendMail(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images, JavaMailSender javaMailSender) throws ThingsboardException { if (apiUsageStateService.getApiUsageState(tenantId).isEmailSendEnabled()) { try { - MimeMessage mailMsg = mailSender.createMimeMessage(); + MimeMessage mailMsg = javaMailSender.createMimeMessage(); boolean multipart = (images != null && !images.isEmpty()); MimeMessageHelper helper = new MimeMessageHelper(mailMsg, multipart, "UTF-8"); helper.setFrom(StringUtils.isBlank(from) ? mailFrom : from); @@ -251,7 +261,7 @@ public class DefaultMailService implements MailService { helper.setSubject(subject); helper.setText(body, isHtml); - if (images != null && images.size() > 0) { + if (multipart) { for (String imgId : images.keySet()) { String imgValue = images.get(imgId); String value = imgValue.replaceFirst("^data:image/[^;]*;base64,?", ""); @@ -261,7 +271,7 @@ public class DefaultMailService implements MailService { helper.addInline(imgId, iss, contentType); } } - mailSender.send(helper.getMimeMessage()); + javaMailSender.send(helper.getMimeMessage()); apiUsageClient.report(tenantId, ApiUsageRecordKey.EMAIL_EXEC_COUNT, 1); } catch (Exception e) { throw handleException(e); 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 e125927af4..590c864787 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.api; import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.mail.javamail.JavaMailSender; import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageStateMailMessage; import org.thingsboard.server.common.data.ApiUsageStateValue; @@ -44,5 +45,7 @@ public interface MailService { void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images) throws ThingsboardException; + void send(TenantId tenantId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images, JavaMailSender javaMailSender) throws ThingsboardException; + void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index c9da3cc81a..6e89140ea7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -32,7 +32,6 @@ import org.thingsboard.server.common.msg.TbMsg; import java.io.IOException; import java.util.HashMap; import java.util.Map; -import java.util.regex.Pattern; import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS; import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; @@ -52,19 +51,18 @@ import static org.thingsboard.rule.engine.mail.TbSendEmailNode.SEND_EMAIL_TYPE; public class TbMsgToEmailNode implements TbNode { private static final ObjectMapper MAPPER = new ObjectMapper(); - - private static final String ATTACHMENTS = "attachments"; private static final String IMAGES = "images"; - private static final String EMAIL_TIMEZONE = "emailTimezone"; - - private static final Pattern dateVarPattern = Pattern.compile("%d\\{([^\\}]*)\\}"); private TbMsgToEmailNodeConfiguration config; + private boolean isDynamicHtmlTemplate = false; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class); - } + if(this.config.getMailBodyType().equals("dynamic")) { + this.isDynamicHtmlTemplate = true; + } + } @Override public void onMsg(TbContext ctx, TbMsg msg) { @@ -89,7 +87,11 @@ public class TbMsgToEmailNode implements TbNode { builder.to(fromTemplate(this.config.getToTemplate(), msg)); builder.cc(fromTemplate(this.config.getCcTemplate(), msg)); builder.bcc(fromTemplate(this.config.getBccTemplate(), msg)); - builder.html(Boolean.parseBoolean(fromTemplate(this.config.getIsHtmlTemplate(), msg))); + if(isDynamicHtmlTemplate) { + builder.html(Boolean.parseBoolean(fromTemplate(this.config.getIsHtmlTemplate(), msg))); + } else { + builder.html(Boolean.parseBoolean(this.config.getMailBodyType())); + } builder.subject(fromTemplate(this.config.getSubjectTemplate(), msg)); builder.body(fromTemplate(this.config.getBodyTemplate(), msg)); String imagesStr = msg.getMetaData().getValue(IMAGES); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java index 0ac1a379ec..7ad2391c87 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java @@ -28,6 +28,7 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { private String subjectTemplate; private String bodyTemplate; private String isHtmlTemplate; + private String mailBodyType; @Override public TbMsgToEmailNodeConfiguration defaultConfiguration() { @@ -37,6 +38,7 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { configuration.subjectTemplate = "Device ${deviceType} temperature high"; configuration.bodyTemplate = "Device ${deviceName} has high temperature ${temp}"; configuration.isHtmlTemplate = "${isHtml}"; + configuration.mailBodyType = "dynamic"; return configuration; } } 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 543e68e1b3..fc9842da63 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 @@ -18,9 +18,7 @@ package org.thingsboard.rule.engine.mail; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.core.io.InputStreamSource; import org.springframework.mail.javamail.JavaMailSenderImpl; -import org.springframework.mail.javamail.MimeMessageHelper; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; @@ -30,10 +28,7 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import javax.mail.internet.MimeMessage; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.Map; import java.util.Properties; import static org.thingsboard.common.util.DonAsynchron.withCallback; @@ -93,31 +88,8 @@ public class TbSendEmailNode implements TbNode { ctx.getMailService().send(ctx.getTenantId(), email.getFrom(), email.getTo(), email.getCc(), email.getBcc(), email.getSubject(), email.getBody(), email.isHtml(), email.getImages()); } else { - MimeMessage mailMsg = mailSender.createMimeMessage(); - boolean multipart = (email.getImages() != null && !email.getImages().isEmpty()); - MimeMessageHelper helper = new MimeMessageHelper(mailMsg, multipart, "UTF-8"); - helper.setFrom(email.getFrom()); - helper.setTo(email.getTo().split("\\s*,\\s*")); - if (!StringUtils.isBlank(email.getCc())) { - helper.setCc(email.getCc().split("\\s*,\\s*")); - } - if (!StringUtils.isBlank(email.getBcc())) { - helper.setBcc(email.getBcc().split("\\s*,\\s*")); - } - helper.setSubject(email.getSubject()); - helper.setText(email.getBody(), email.isHtml()); - if (email.getImages() != null && email.getImages().size() > 0) { - Map images = email.getImages(); - for (String imgId : images.keySet()) { - String imgValue = images.get(imgId); - String value = imgValue.replaceFirst("^data:image/[^;]*;base64,?", ""); - byte[] bytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(value); - String contentType = helper.getFileTypeMap().getContentType(imgId); - InputStreamSource iss = () -> new ByteArrayInputStream(bytes); - helper.addInline(imgId, iss, contentType); - } - } - mailSender.send(helper.getMimeMessage()); + ctx.getMailService().send(ctx.getTenantId(), email.getFrom(), email.getTo(), email.getCc(), + email.getBcc(), email.getSubject(), email.getBody(), email.isHtml(), email.getImages(), this.mailSender); } } From 8281609df0ce08f2b0ae6dbe55498e0f79581a10 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 21 Apr 2021 13:05:31 +0300 Subject: [PATCH 003/231] Join front-end --- .../org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java | 6 ++---- .../rule/engine/mail/TbMsgToEmailNodeConfiguration.java | 3 +-- .../public/static/rulenode/rulenode-core-config.js | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java index 6e89140ea7..bc2372a2a6 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNode.java @@ -54,14 +54,12 @@ public class TbMsgToEmailNode implements TbNode { private static final String IMAGES = "images"; private TbMsgToEmailNodeConfiguration config; - private boolean isDynamicHtmlTemplate = false; + private boolean isDynamicHtmlTemplate; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbMsgToEmailNodeConfiguration.class); - if(this.config.getMailBodyType().equals("dynamic")) { - this.isDynamicHtmlTemplate = true; - } + this.isDynamicHtmlTemplate = this.config.getMailBodyType().equals("dynamic"); } @Override diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java index 7ad2391c87..51b34f7139 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeConfiguration.java @@ -37,8 +37,7 @@ public class TbMsgToEmailNodeConfiguration implements NodeConfiguration { configuration.toTemplate = "${userEmail}"; configuration.subjectTemplate = "Device ${deviceType} temperature high"; configuration.bodyTemplate = "Device ${deviceName} has high temperature ${temp}"; - configuration.isHtmlTemplate = "${isHtml}"; - configuration.mailBodyType = "dynamic"; + configuration.mailBodyType = "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..494ee4715f 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){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,H=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 G,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"}(G||(G={})),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=H,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=H,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=G,n.fetchModes=Object.keys(G),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===G.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),He=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),Ge=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,He,Ge],imports:[r.CommonModule,a.SharedModule,ue],exports:[Me,we,Re,De,Oe,Ke,Be,He,Ge]})],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){var t=this;this.selectorArray=[{name:"Plaim Text",value:!1},{name:"HTML",value:!0},{name:"Dynamic",value:"dynamic"}],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]],mailBodyType:[e?e.subjectTemplate:null],isHtmlTemplate:[e?e.isHtmlTemplate:null],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]}),this.toEmailConfigForm.get("mailBodyType").valueChanges.subscribe((function(){t.toEmailConfigForm.get("isHtmlTemplate").patchValue("",{emitEvent:!1})}))},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 \n Mail body type\n \n \n {{value.name}}\n \n \n \n \n tb.rulenode.html-template\n \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","html-template":"HTML template","dynamic-html-template":"Dynamic HTML template","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=He,e.ɵcd=Ge,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 2423398d418a2b1469b8083dbd965ae56c198223 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 21 Apr 2021 13:14:00 +0300 Subject: [PATCH 004/231] Added missing parameter to test --- .../org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java index 3385d20d3a..c29e45f569 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mail/TbMsgToEmailNodeTest.java @@ -97,6 +97,7 @@ public class TbMsgToEmailNodeTest { config.setToTemplate("${userEmail}"); config.setSubjectTemplate("Hi ${username} there"); config.setBodyTemplate("${name} is to high. Current ${passed} and ${count}"); + config.setMailBodyType("false"); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); From c865db244c028a824c33cf00748162147bae7ea4 Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Thu, 15 Apr 2021 15:55:41 +0300 Subject: [PATCH 005/231] Edge Downlinks: fixed types, added error handler, refactored --- ui-ngx/src/app/core/http/entity.service.ts | 10 +-- .../edge/edge-downlink-table-config.ts | 63 +++++++++++-------- .../edge-downlink-table-header.component.ts | 4 -- .../edge/edge-downlink-table.component.ts | 23 ++++--- .../assets/locale/locale.constant-en_US.json | 2 +- 5 files changed, 55 insertions(+), 47 deletions(-) diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 90275f1016..592dbc9bca 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -19,7 +19,7 @@ import { EMPTY, forkJoin, Observable, of, throwError } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PageLink } from '@shared/models/page/page-link'; import { AliasEntityType, EntityType } from '@shared/models/entity-type.models'; -import { BaseData } from '@shared/models/base-data'; +import { BaseData, HasId } from '@shared/models/base-data'; import { EntityId } from '@shared/models/id/entity-id'; import { DeviceService } from '@core/http/device.service'; import { TenantService } from '@core/http/tenant.service'; @@ -76,8 +76,8 @@ import { } from '@shared/models/query/query.models'; import { alarmFields } from '@shared/models/alarm.models'; import { EdgeService } from "@core/http/edge.service"; -import { Edge, EdgeEventType } from '@shared/models/edge.models'; -import { RuleChainType } from "@shared/models/rule-chain.models"; +import { Edge, EdgeEvent, EdgeEventType } from '@shared/models/edge.models'; +import { RuleChainMetaData, RuleChainType } from "@shared/models/rule-chain.models"; import { WidgetService } from "@core/http/widget.service"; import { DeviceProfileService } from "@core/http/device-profile.service"; @@ -1333,8 +1333,8 @@ export class EntityService { return entitiesObservable; } - public getEdgeEventContentByEntityType(entity: any): Observable { - let entityObservable: Observable; + public getEdgeEventContent(entity: EdgeEvent): Observable | RuleChainMetaData | string> { + let entityObservable: Observable | RuleChainMetaData | string>; const entityId: string = entity.entityId; const entityType: any = entity.type; switch (entityType) { diff --git a/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts b/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts index 8f2f9a776d..517415a68a 100644 --- a/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts @@ -22,6 +22,7 @@ import { } from '@home/models/entity/entities-table-config.models'; import { EdgeEvent, + EdgeEventActionType, edgeEventActionTypeTranslations, EdgeEventStatus, edgeEventStatusColor, @@ -33,7 +34,6 @@ import { TranslateService } from '@ngx-translate/core'; import { DatePipe } from '@angular/common'; import { MatDialog } from '@angular/material/dialog'; import { EntityId } from '@shared/models/id/entity-id'; -import { EntityTypeResource } from '@shared/models/entity-type.models'; import { Observable } from 'rxjs'; import { PageData } from '@shared/models/page/page-data'; import { Direction } from '@shared/models/page/sort-order'; @@ -49,18 +49,22 @@ import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-dow import { EdgeService } from '@core/http/edge.service'; import { map } from 'rxjs/operators'; import { EntityService } from "@core/http/entity.service"; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ActionNotificationShow } from '@core/notification/notification.actions'; export class EdgeDownlinkTableConfig extends EntityTableConfig { queueStartTs: number; - constructor(private edgeService: EdgeService, - private entityService: EntityService, - private dialogService: DialogService, - private translate: TranslateService, - private attributeService: AttributeService, + constructor(private attributeService: AttributeService, private datePipe: DatePipe, + private dialogService: DialogService, private dialog: MatDialog, + private edgeService: EdgeService, + private entityService: EntityService, + private translate: TranslateService, + private store: Store, public entityId: EntityId) { super(); @@ -74,12 +78,9 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig; + this.entityTranslations = { noEntities: 'edge.no-downlinks-prompt' }; this.entitiesFetchFunction = pageLink => this.fetchEvents(pageLink); - this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC}; + this.defaultSortOrder = { property: 'createdTime', direction: Direction.DESC }; this.updateColumns(); } @@ -96,7 +97,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig this.isEdgeEventHasData(entity.type), + isEnabled: (entity) => this.isEdgeEventHasData(entity), onAction: ($event, entity) => { - this.prepareEdgeEventContent(entity).subscribe((content) => { - this.showEdgeEventContent($event, content,'event.data'); - }); + this.prepareEdgeEventContent(entity).subscribe( + (content) => this.showEdgeEventContent($event, content,'event.data'), + () => this.showEntityNotFoundError() + ); } }, '40px'), @@ -141,7 +143,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig this.queueStartTs; } - isEdgeEventHasData(edgeEventType: EdgeEventType): boolean { - switch (edgeEventType) { - case EdgeEventType.ADMIN_SETTINGS: - return false; - default: - return true; - } + isEdgeEventHasData(entity: EdgeEvent): boolean { + return !(entity.type === EdgeEventType.ADMIN_SETTINGS || + entity.action === EdgeEventActionType.DELETED); } - prepareEdgeEventContent(entity: any): Observable { - return this.entityService.getEdgeEventContentByEntityType(entity).pipe( + prepareEdgeEventContent(entity: EdgeEvent): Observable { + return this.entityService.getEdgeEventContent(entity).pipe( map((result) => JSON.stringify(result)) ); } @@ -182,4 +180,15 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig) { super(store); } - - eventTypeChanged() { - this.eventTableConfig.table.resetSortAndFilter(true, true); - } } diff --git a/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table.component.ts b/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table.component.ts index 3b5fca1c7a..cac27c55de 100644 --- a/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table.component.ts +++ b/ui-ngx/src/app/modules/home/components/edge/edge-downlink-table.component.ts @@ -25,6 +25,8 @@ import { DialogService } from '@core/services/dialog.service'; import { AttributeService } from '@core/http/attribute.service'; import { EdgeService } from '@core/http/edge.service'; import { EntityService } from "@core/http/entity.service"; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; @Component({ selector: 'tb-edge-downlink-table', @@ -64,27 +66,28 @@ export class EdgeDownlinkTableComponent implements OnInit { edgeDownlinkTableConfig: EdgeDownlinkTableConfig; - constructor(private edgeService: EdgeService, - private entityService: EntityService, + constructor(private attributeService: AttributeService, + private datePipe: DatePipe, private dialogService: DialogService, + private dialog: MatDialog, + private edgeService: EdgeService, + private entityService: EntityService, private translate: TranslateService, - private attributeService: AttributeService, - private datePipe: DatePipe, - private dialog: MatDialog) { + protected store: Store) { } ngOnInit() { this.dirtyValue = !this.activeValue; this.edgeDownlinkTableConfig = new EdgeDownlinkTableConfig( - this.edgeService, - this.entityService, - this.dialogService, - this.translate, this.attributeService, this.datePipe, + this.dialogService, this.dialog, + this.edgeService, + this.entityService, + this.translate, + this.store, this.entityIdValue ); } - } 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 5ee15c371d..1149dcd84d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1300,7 +1300,7 @@ "make-private-edge-text": "After the confirmation the edge and all its data will be made private and won't be accessible by others.", "import": "Import edge", "label": "Label", - "load-entity-error": "Failed to load data. Entity not found or has been deleted.", + "load-entity-error": "Failed to load data. Entity has been deleted.", "assign-new-edge": "Assign new edge", "manage-edge-dashboards": "Edge dashboards", "unassign-from-edge": "Unassign from edge", From 287a0a7515bb0cc3e150efffe2ebc07567c23baa Mon Sep 17 00:00:00 2001 From: Andrew Volostnykh Date: Thu, 6 May 2021 16:54:30 +0300 Subject: [PATCH 006/231] Added front --- .../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 3d86444d63..71039c581e 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.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})})); + ***************************************************************************** */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,H=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 G,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"}(G||(G={})),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=H,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=H,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=G,n.fetchModes=Object.keys(G),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===G.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),He=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),Ge=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,He,Ge],imports:[r.CommonModule,a.SharedModule,ue],exports:[Me,we,Re,De,Oe,Ke,Be,He,Ge]})],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){var t=this;this.selectorArray=[{name:"Plaim Text",value:!1},{name:"HTML",value:!0},{name:"Dynamic",value:"dynamic"}],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]],mailBodyType:[e?e.subjectTemplate:null],isHtmlTemplate:[e?e.isHtmlTemplate:null],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]}),this.toEmailConfigForm.get("mailBodyType").valueChanges.subscribe((function(){t.toEmailConfigForm.get("isHtmlTemplate").patchValue("",{emitEvent:!1})}))},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 \n tb.rulenode.mail-body-type\n \n \n {{value.name}}\n \n \n \n \n tb.rulenode.html-template\n \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","html-template":"HTML template","dynamic-html-template":"Dynamic HTML template","mail-body-type":"Mail body type","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=He,e.ɵcd=Ge,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 f81413112c2858e44dc308c056c38f8fbc5ed94f Mon Sep 17 00:00:00 2001 From: Artem Babak Date: Wed, 26 May 2021 21:02:35 +0300 Subject: [PATCH 007/231] Edge: menu order changes --- ui-ngx/src/app/core/services/menu.service.ts | 32 +++++++++---------- .../pages/customer/customer-routing.module.ts | 2 +- .../customers-table-config.resolver.ts | 2 +- .../dashboards-table-config.resolver.ts | 2 +- .../home/pages/edge/edge-routing.module.ts | 26 +++++++++++++-- .../pages/edge/edges-table-config.resolver.ts | 2 +- .../rulechain/rule-node-details.component.ts | 2 +- .../rulechain/rulechain-page.component.ts | 2 +- .../rulechains-table-config.resolver.ts | 6 ++-- .../pages/rulechain/rulenode.component.ts | 2 +- 10 files changed, 49 insertions(+), 29 deletions(-) diff --git a/ui-ngx/src/app/core/services/menu.service.ts b/ui-ngx/src/app/core/services/menu.service.ts index 07a6d8f237..10dedd4fc7 100644 --- a/ui-ngx/src/app/core/services/menu.service.ts +++ b/ui-ngx/src/app/core/services/menu.service.ts @@ -291,26 +291,26 @@ export class MenuService { ); if (authState.edgesSupportEnabled) { sections.push( + { + id: guid(), + name: 'edge.edge-instances', + type: 'link', + path: '/edgeInstances', + icon: 'router' + }, { id: guid(), name: 'edge.management', type: 'toggle', - path: '/edges', - height: '80px', - icon: 'router', + path: '/edgeManagement', + height: '40px', + icon: 'settings_input_antenna', pages: [ - { - id: guid(), - name: 'edge.edge-instances', - type: 'link', - path: '/edges', - icon: 'router' - }, { id: guid(), name: 'edge.rulechain-templates', type: 'link', - path: '/edges/ruleChains', + path: '/edgeManagement/ruleChains', icon: 'settings_ethernet' } ] @@ -448,12 +448,12 @@ export class MenuService { { name: 'edge.edge-instances', icon: 'router', - path: '/edges' + path: '/edgeInstances' }, { name: 'edge.rulechain-templates', icon: 'settings_ethernet', - path: '/edges/ruleChains' + path: '/edgeManagement/ruleChains' } ] } @@ -548,7 +548,7 @@ export class MenuService { id: guid(), name: 'edge.edge-instances', type: 'link', - path: '/edges', + path: '/edgeInstances', icon: 'router' } ); @@ -606,8 +606,8 @@ export class MenuService { places: [ { name: 'edge.edge-instances', - icon: 'router', - path: '/edges' + icon: 'settings_input_antenna', + path: '/edgeInstances' } ] } diff --git a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts index f5f3ee05fa..2ed306c2d8 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customer-routing.module.ts @@ -98,7 +98,7 @@ const routes: Routes = [ } }, { - path: ':customerId/edges', + path: ':customerId/edgeInstances', component: EntitiesTableComponent, data: { auth: [Authority.TENANT_ADMIN], diff --git a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts index 8ac9e64390..5a6e03b3d7 100644 --- a/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts @@ -169,7 +169,7 @@ export class CustomersTableConfigResolver implements Resolve): boolean { diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts index c572fa958f..a4c17e75ac 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts @@ -362,7 +362,7 @@ export class DashboardsTableConfigResolver implements Resolve) { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts index 00cd38b9c4..d5a3fcc8a0 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.ts @@ -152,7 +152,7 @@ export class RuleNodeDetailsComponent extends PageComponent implements OnInit, O } if (this.ruleNode.targetRuleChainId) { if (this.ruleChainType === RuleChainType.EDGE) { - this.router.navigateByUrl(`/edges/ruleChains/${this.ruleNode.targetRuleChainId}`); + this.router.navigateByUrl(`/edgeManagement/ruleChains/${this.ruleNode.targetRuleChainId}`); } else { this.router.navigateByUrl(`/ruleChains/${this.ruleNode.targetRuleChainId}`); } diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts index 54102ffbcd..ea9cc54747 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechain-page.component.ts @@ -1289,7 +1289,7 @@ export class RuleChainPageComponent extends PageComponent if (this.ruleChainType !== RuleChainType.EDGE) { this.router.navigateByUrl(`ruleChains/${this.ruleChain.id.id}`); } else { - this.router.navigateByUrl(`edges/ruleChains/${this.ruleChain.id.id}`); + this.router.navigateByUrl(`edgeManagement/ruleChains/${this.ruleChain.id.id}`); } } else { this.createRuleChainModel(); diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts index 37a21cab2e..ffc05052fe 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts @@ -285,7 +285,7 @@ export class RuleChainsTableConfigResolver implements Resolve Date: Thu, 27 May 2021 09:39:53 +0300 Subject: [PATCH 008/231] Minor code cleanup --- .../src/app/modules/home/pages/edge/edge-routing.module.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts b/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts index bd96b953a8..4ffa51a6bd 100644 --- a/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts +++ b/ui-ngx/src/app/modules/home/pages/edge/edge-routing.module.ts @@ -39,7 +39,6 @@ import { RuleNodeComponentsResolver, TooltipsterResolver } from '@home/pages/rulechain/rulechain-routing.module'; -import { CustomersTableConfigResolver } from '@home/pages/customer/customers-table-config.resolver'; const routes: Routes = [ { @@ -56,7 +55,7 @@ const routes: Routes = [ component: EntitiesTableComponent, data: { auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], - edgesType: 'tenant', + edgesType: 'tenant' }, resolve: { entitiesTableConfig: EdgesTableConfigResolver @@ -272,8 +271,7 @@ const routes: Routes = [ ] } ] - } - ]; + }]; @NgModule({ imports: [RouterModule.forChild(routes)], From 0c886656543987f27d59c2c053c11677de147a8b Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 25 May 2021 19:44:34 +0300 Subject: [PATCH 009/231] js-executor: scriptMap refactored from Object to the Map() --- .../api/jsInvokeMessageProcessor.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index 2964291a30..a760e21e47 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -34,10 +34,9 @@ const slowQueryLogBody = config.get('script.slow_query_log_body') === 'true'; const {performance} = require('perf_hooks'); function JsInvokeMessageProcessor(producer) { - console.log("Producer:", producer); this.producer = producer; this.executor = new JsExecutor(useSandbox); - this.scriptMap = {}; + this.scriptMap = new Map(); this.scriptIds = []; this.executedScriptsCounter = 0; } @@ -157,12 +156,12 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function(requestId, re JsInvokeMessageProcessor.prototype.processReleaseRequest = function(requestId, responseTopic, headers, releaseRequest) { var scriptId = getScriptId(releaseRequest); logger.debug('[%s] Processing release request, scriptId: [%s]', requestId, scriptId); - if (this.scriptMap[scriptId]) { + if (this.scriptMap.has(scriptId)) { var index = this.scriptIds.indexOf(scriptId); if (index > -1) { this.scriptIds.splice(index, 1); } - delete this.scriptMap[scriptId]; + this.scriptMap.delete(scriptId); } var releaseResponse = createReleaseResponse(scriptId, true); logger.debug('[%s] Sending success release response, scriptId: [%s]', requestId, scriptId); @@ -189,8 +188,8 @@ JsInvokeMessageProcessor.prototype.sendResponse = function (requestId, responseT JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scriptBody) { var self = this; return new Promise(function(resolve, reject) { - if (self.scriptMap[scriptId]) { - resolve(self.scriptMap[scriptId]); + if (self.scriptMap.has(scriptId)) { + resolve(self.scriptMap.get(scriptId)); } else { self.executor.compileScript(scriptBody).then( (script) => { @@ -206,16 +205,17 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scrip } JsInvokeMessageProcessor.prototype.cacheScript = function(scriptId, script) { - if (!this.scriptMap[scriptId]) { + if (!this.scriptMap.has(scriptId)) { this.scriptIds.push(scriptId); while (this.scriptIds.length > maxActiveScripts) { logger.info('Active scripts count [%s] exceeds maximum limit [%s]', this.scriptIds.length, maxActiveScripts); const prevScriptId = this.scriptIds.shift(); logger.info('Removing active script with id [%s]', prevScriptId); - delete this.scriptMap[prevScriptId]; + this.scriptMap.delete(prevScriptId); } } - this.scriptMap[scriptId] = script; + this.scriptMap.set(scriptId, script); + logger.info("scriptMap size is [%s]", this.scriptMap.size); } function createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse) { From d729d9ee959963b570aeeaf27787cdd6b6f44bdf Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 27 May 2021 14:50:42 +0300 Subject: [PATCH 010/231] js-executor: instrumentation event for producer and consumer to define the exact flow how to Kafka works without batches (for debug only) --- msa/js-executor/queue/kafkaTemplate.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index 2f4fd8751d..1fd21185d0 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -114,6 +114,30 @@ function KafkaProducer() { consumer = kafkaClient.consumer({groupId: 'js-executor-group'}); producer = kafkaClient.producer(); + + const { CONNECT } = producer.events; + const removeListenerC = producer.on(CONNECT, e => logger.info(`producer CONNECT`)); + const { DISCONNECT } = producer.events; + const removeListenerD = producer.on(DISCONNECT, e => logger.info(`producer DISCONNECT`)); + const { REQUEST } = producer.events; + const removeListenerR = producer.on(REQUEST, e => logger.info(`producer REQUEST ${e.payload.broker}`)); + const { REQUEST_TIMEOUT } = producer.events; + const removeListenerRT = producer.on(REQUEST_TIMEOUT, e => logger.info(`producer REQUEST_TIMEOUT ${e.payload.broker}`)); + const { REQUEST_QUEUE_SIZE } = producer.events; + const removeListenerRQS = producer.on(REQUEST_QUEUE_SIZE, e => logger.info(`producer REQUEST_QUEUE_SIZE ${e.payload.broker} size ${e.queueSize}`)); + + const removeListeners = {} + const { FETCH_START } = consumer.events; + removeListeners[FETCH_START] = consumer.on(FETCH_START, e => logger.info(`consumer FETCH_START`)); + const { FETCH } = consumer.events; + removeListeners[FETCH] = consumer.on(FETCH, e => logger.info(`consumer FETCH numberOfBatches ${e.payload.numberOfBatches} duration ${e.payload.duration}`)); + const { START_BATCH_PROCESS } = consumer.events; + removeListeners[START_BATCH_PROCESS] = consumer.on(START_BATCH_PROCESS, e => logger.info(`consumer START_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); + const { END_BATCH_PROCESS } = consumer.events; + removeListeners[END_BATCH_PROCESS] = consumer.on(END_BATCH_PROCESS, e => logger.info(`consumer END_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); + const { COMMIT_OFFSETS } = consumer.events; + removeListeners[COMMIT_OFFSETS] = consumer.on(COMMIT_OFFSETS, e => logger.info(`consumer COMMIT_OFFSETS topics ${e.payload.topics}`)); + const messageProcessor = new JsInvokeMessageProcessor(new KafkaProducer()); await consumer.connect(); await producer.connect(); From 35e2ff99c3d3f7a488fdc4d3fb3375daa06d41f9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 27 May 2021 14:55:10 +0300 Subject: [PATCH 011/231] js-executor: send messages as batch --- .../api/jsInvokeMessageProcessor.js | 25 ++++---- msa/js-executor/queue/kafkaTemplate.js | 63 ++++++++++++++----- 2 files changed, 63 insertions(+), 25 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index a760e21e47..867dd82b39 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -172,17 +172,20 @@ JsInvokeMessageProcessor.prototype.sendResponse = function (requestId, responseT 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); - logger.error(err.stack); - } - } - ); + logger.debug('[%s] Sending response to queue, scriptId: [%s]', requestId, scriptId); + this.producer.send(responseTopic, scriptId, rawResponse, headers); +//TODO put error msg for other queues implementation except Kafka +// .then( +// () => { +// logger.info('[%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); +// logger.error(err.stack); +// } +// } +// ); } JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scriptBody) { diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index 1fd21185d0..da46a45301 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -33,24 +33,54 @@ let producer; const configEntries = []; +let topicMessages = []; +let loopSend; + function KafkaProducer() { - this.send = async (responseTopic, scriptId, rawResponse, headers) => { - return producer.send( - { - topic: responseTopic, - acks: acks, - compression: compressionType, - messages: [ - { - key: scriptId, - value: rawResponse, - headers: headers.data - } - ] - }); + this.send = (responseTopic, scriptId, rawResponse, headers) => { + logger.debug('Pending queue response, scriptId: [%s]', scriptId); + const message = { + topic: responseTopic, + messages: [{ + key: scriptId, + value: rawResponse, + headers: headers.data + }] + }; + + topicMessages.push(message); + return {}; } } +function sendLoopFunction() { + loopSend = setInterval(sendProducerMsg, 200); +} + +function sendProducerMsg() { + if (topicMessages.length > 0) { + logger.info('sendProducerMsg from queue response, lenght: [%s]', topicMessages.length ); + const messagesToSend = topicMessages; + topicMessages = []; + producer.sendBatch({ + topicMessages: messagesToSend, + acks: acks, + compression: compressionType + }).then( + () => { + logger.info('Response sent to kafka, length: [%s]', messagesToSend.length ); + }, + (err) => { + if (err) { + logger.error('Failed to send kafka, length: [%s], pending to reprocess msgs', messagesToSend.length ); + topicMessages = messagesToSend.concat(topicMessages); + logger.error(err.stack); + } + } + ); + } +} + (async () => { try { logger.info('Starting ThingsBoard JavaScript Executor Microservice...'); @@ -141,6 +171,7 @@ function KafkaProducer() { const messageProcessor = new JsInvokeMessageProcessor(new KafkaProducer()); await consumer.connect(); await producer.connect(); + sendLoopFunction(); await consumer.subscribe({topic: requestTopic}); logger.info('Started ThingsBoard JavaScript Executor Microservice.'); @@ -221,6 +252,10 @@ async function disconnectProducer() { var _producer = producer; producer = null; try { + logger.info('Stopping loop...'); + //TODO: send handle msg + clearInterval(loopSend); + sendProducerMsg(); await _producer.disconnect(); logger.info('Kafka Producer stopped.'); } catch (e) { From 0970ce65b448f622056c070831aa1625f1e81f36 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 27 May 2021 18:45:29 +0300 Subject: [PATCH 012/231] js-executor: maxBatchSize --- msa/js-executor/queue/kafkaTemplate.js | 88 ++++++++++++++++---------- 1 file changed, 53 insertions(+), 35 deletions(-) diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index da46a45301..a061215b28 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -26,6 +26,9 @@ 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; +const linger = 5; //milliseconds //TODO move to the config +const maxBatchSize = 10; //max messages in batch //TODO move to the config + let kafkaClient; let kafkaAdmin; let consumer; @@ -33,8 +36,8 @@ let producer; const configEntries = []; -let topicMessages = []; -let loopSend; +let batchMessages = []; +let sendLoopInstance; function KafkaProducer() { this.send = (responseTopic, scriptId, rawResponse, headers) => { @@ -48,36 +51,51 @@ function KafkaProducer() { }] }; - topicMessages.push(message); + pushMessageToSendLater(message); return {}; } } -function sendLoopFunction() { - loopSend = setInterval(sendProducerMsg, 200); +function pushMessageToSendLater(message) { + batchMessages.push(message); + if (batchMessages.length >= maxBatchSize) { + sendMessagesAsBatch(); + sendLoopWithLinger(); //reset loop function and reschedule new linger + } +} + +function sendLoopWithLinger() { + if (sendLoopInstance) { + logger.debug("Clear sendLoop scheduler. Starting new send loop with linger [%s]", linger); + clearInterval(sendLoopInstance); + } else { + logger.debug("Starting new send loop with linger [%s]", linger) + } + sendLoopInstance = setInterval(sendMessagesAsBatch, linger); } -function sendProducerMsg() { - if (topicMessages.length > 0) { - logger.info('sendProducerMsg from queue response, lenght: [%s]', topicMessages.length ); - const messagesToSend = topicMessages; - topicMessages = []; +function sendMessagesAsBatch() { + if (batchMessages.length > 0) { + logger.info('sendMessagesAsBatch, lenght: [%s]', batchMessages.length ); + const messagesToSend = batchMessages; + batchMessages = []; producer.sendBatch({ topicMessages: messagesToSend, acks: acks, compression: compressionType }).then( - () => { - logger.info('Response sent to kafka, length: [%s]', messagesToSend.length ); - }, - (err) => { - if (err) { - logger.error('Failed to send kafka, length: [%s], pending to reprocess msgs', messagesToSend.length ); - topicMessages = messagesToSend.concat(topicMessages); - logger.error(err.stack); - } - } - ); + () => { + logger.info('Response sent to kafka, length: [%s]', messagesToSend.length ); + }, + (err) => { + logger.error('Failed to send kafka, length: [%s], pending to reprocess msgs', messagesToSend.length ); + batchMessages = messagesToSend.concat(batchMessages); + logger.error(err.stack); + } + ); + + } else { + //logger.debug("nothing to send"); } } @@ -156,22 +174,22 @@ function sendProducerMsg() { const { REQUEST_QUEUE_SIZE } = producer.events; const removeListenerRQS = producer.on(REQUEST_QUEUE_SIZE, e => logger.info(`producer REQUEST_QUEUE_SIZE ${e.payload.broker} size ${e.queueSize}`)); - const removeListeners = {} - const { FETCH_START } = consumer.events; - removeListeners[FETCH_START] = consumer.on(FETCH_START, e => logger.info(`consumer FETCH_START`)); - const { FETCH } = consumer.events; - removeListeners[FETCH] = consumer.on(FETCH, e => logger.info(`consumer FETCH numberOfBatches ${e.payload.numberOfBatches} duration ${e.payload.duration}`)); - const { START_BATCH_PROCESS } = consumer.events; - removeListeners[START_BATCH_PROCESS] = consumer.on(START_BATCH_PROCESS, e => logger.info(`consumer START_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); - const { END_BATCH_PROCESS } = consumer.events; - removeListeners[END_BATCH_PROCESS] = consumer.on(END_BATCH_PROCESS, e => logger.info(`consumer END_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); - const { COMMIT_OFFSETS } = consumer.events; - removeListeners[COMMIT_OFFSETS] = consumer.on(COMMIT_OFFSETS, e => logger.info(`consumer COMMIT_OFFSETS topics ${e.payload.topics}`)); +// const removeListeners = {} +// const { FETCH_START } = consumer.events; +// removeListeners[FETCH_START] = consumer.on(FETCH_START, e => logger.info(`consumer FETCH_START`)); +// const { FETCH } = consumer.events; +// removeListeners[FETCH] = consumer.on(FETCH, e => logger.info(`consumer FETCH numberOfBatches ${e.payload.numberOfBatches} duration ${e.payload.duration}`)); +// const { START_BATCH_PROCESS } = consumer.events; +// removeListeners[START_BATCH_PROCESS] = consumer.on(START_BATCH_PROCESS, e => logger.info(`consumer START_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); +// const { END_BATCH_PROCESS } = consumer.events; +// removeListeners[END_BATCH_PROCESS] = consumer.on(END_BATCH_PROCESS, e => logger.info(`consumer END_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); +// const { COMMIT_OFFSETS } = consumer.events; +// removeListeners[COMMIT_OFFSETS] = consumer.on(COMMIT_OFFSETS, e => logger.info(`consumer COMMIT_OFFSETS topics ${e.payload.topics}`)); const messageProcessor = new JsInvokeMessageProcessor(new KafkaProducer()); await consumer.connect(); await producer.connect(); - sendLoopFunction(); + sendLoopWithLinger(); await consumer.subscribe({topic: requestTopic}); logger.info('Started ThingsBoard JavaScript Executor Microservice.'); @@ -254,8 +272,8 @@ async function disconnectProducer() { try { logger.info('Stopping loop...'); //TODO: send handle msg - clearInterval(loopSend); - sendProducerMsg(); + clearInterval(sendLoopInstance); + sendMessagesAsBatch(); await _producer.disconnect(); logger.info('Kafka Producer stopped.'); } catch (e) { From 93bea7020584343511b1a561bb861fd010df037e Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 28 May 2021 09:41:35 +0300 Subject: [PATCH 013/231] js-executor: added parameters for producer TB_KAFKA_BATCH_SIZE and TB_KAFKA_LINGER_MS; added print stats frequency SCRIPT_STAT_PRINT_FREQUENCY --- .../api/jsInvokeMessageProcessor.js | 17 +++++--- .../config/custom-environment-variables.yml | 3 ++ msa/js-executor/config/default.yml | 7 ++- msa/js-executor/queue/kafkaTemplate.js | 43 +++++++++++-------- 4 files changed, 44 insertions(+), 26 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index 867dd82b39..dec935c8ad 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -25,6 +25,7 @@ const config = require('config'), Utils = require('./utils'), JsExecutor = require('./jsExecutor'); +const statFrequency = Number(config.get('script.stat_print_frequency')); 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')); @@ -39,6 +40,7 @@ function JsInvokeMessageProcessor(producer) { this.scriptMap = new Map(); this.scriptIds = []; this.executedScriptsCounter = 0; + this.lastStatTime = performance.now(); } JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { @@ -118,11 +120,16 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function(requestId, re var scriptId = getScriptId(invokeRequest); logger.debug('[%s] Processing invoke request, scriptId: [%s]', requestId, scriptId); this.executedScriptsCounter++; - if ( this.executedScriptsCounter >= scriptBodyTraceFrequency ) { - this.executedScriptsCounter = 0; - if (logger.levels[logger.level] >= logger.levels['debug']) { - logger.debug('[%s] Executing script body: [%s]', scriptId, invokeRequest.scriptBody); - } + if (this.executedScriptsCounter % statFrequency == 0) { + var nowMs = performance.now(); + var msSinceLastStat = nowMs - this.lastStatTime; + var requestsPerSec = statFrequency / msSinceLastStat * 1000; //msSinceLastStat can't be zero in the real world + this.lastStatTime = nowMs; + logger.info('STAT[%s]: requests [%s], took [%s]ms, request/s [%s]', this.executedScriptsCounter, statFrequency, msSinceLastStat, requestsPerSec); + } + + if (this.executedScriptsCounter % scriptBodyTraceFrequency == 0) { + logger.info('[%s] Executing script body: [%s]', scriptId, invokeRequest.scriptBody); } this.getOrCompileScript(scriptId, invokeRequest.scriptBody).then( (script) => { diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index 5e2bfe8342..c5eb15e8b9 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -26,6 +26,8 @@ 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 + batch_size: "${TB_KAFKA_BATCH_SIZE:128}" # for producer + linger_ms: "${TB_KAFKA_LINGER_MS:1}" # for producer requestTimeout: "TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS" compression: "TB_QUEUE_KAFKA_COMPRESSION" # gzip or uncompressed topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" @@ -70,6 +72,7 @@ logger: script: use_sandbox: "SCRIPT_USE_SANDBOX" + stat_print_frequency: "SCRIPT_STAT_PRINT_FREQUENCY" script_body_trace_frequency: "SCRIPT_BODY_TRACE_FREQUENCY" max_active_scripts: "MAX_ACTIVE_SCRIPTS" slow_query_log_ms: "SLOW_QUERY_LOG_MS" #1.123456 diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index cdf5b35421..5a2c47a675 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -26,6 +26,8 @@ kafka: servers: "localhost:9092" replication_factor: "1" 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 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" @@ -59,7 +61,8 @@ logger: script: use_sandbox: "true" - script_body_trace_frequency: "1000" + script_body_trace_frequency: "10000" + stat_print_frequency: "10000" max_active_scripts: "1000" - slow_query_log_ms: "1.000000" #millis + slow_query_log_ms: "5.000000" #millis slow_query_log_body: "false" diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index a061215b28..79d87c17b4 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -23,12 +23,11 @@ const replicationFactor = Number(config.get('kafka.replication_factor')); const topicProperties = config.get('kafka.topic_properties'); const kafkaClientId = config.get('kafka.client_id'); const acks = Number(config.get('kafka.acks')); +const maxBatchSize = Number(config.get('kafka.batch_size')); +const linger = Number(config.get('kafka.linger_ms')); const requestTimeout = Number(config.get('kafka.requestTimeout')); const compressionType = (config.get('kafka.requestTimeout') === "gzip") ? CompressionTypes.GZIP : CompressionTypes.None; -const linger = 5; //milliseconds //TODO move to the config -const maxBatchSize = 10; //max messages in batch //TODO move to the config - let kafkaClient; let kafkaAdmin; let consumer; @@ -76,7 +75,9 @@ function sendLoopWithLinger() { function sendMessagesAsBatch() { if (batchMessages.length > 0) { - logger.info('sendMessagesAsBatch, lenght: [%s]', batchMessages.length ); + if (batchMessages.length > 1) { + logger.info('sendMessagesAsBatch, length: [%s]', batchMessages.length); + } const messagesToSend = batchMessages; batchMessages = []; producer.sendBatch({ @@ -85,17 +86,15 @@ function sendMessagesAsBatch() { compression: compressionType }).then( () => { - logger.info('Response sent to kafka, length: [%s]', messagesToSend.length ); + logger.debug('Response sent to kafka, length: [%s]', messagesToSend.length); }, (err) => { - logger.error('Failed to send kafka, length: [%s], pending to reprocess msgs', messagesToSend.length ); + logger.error('Failed to send kafka, length: [%s], pending to reprocess msgs', messagesToSend.length); batchMessages = messagesToSend.concat(batchMessages); logger.error(err.stack); } ); - } else { - //logger.debug("nothing to send"); } } @@ -163,6 +162,8 @@ function sendMessagesAsBatch() { consumer = kafkaClient.consumer({groupId: 'js-executor-group'}); producer = kafkaClient.producer(); +/* + //producer event instrumentation to debug const { CONNECT } = producer.events; const removeListenerC = producer.on(CONNECT, e => logger.info(`producer CONNECT`)); const { DISCONNECT } = producer.events; @@ -173,18 +174,22 @@ function sendMessagesAsBatch() { const removeListenerRT = producer.on(REQUEST_TIMEOUT, e => logger.info(`producer REQUEST_TIMEOUT ${e.payload.broker}`)); const { REQUEST_QUEUE_SIZE } = producer.events; const removeListenerRQS = producer.on(REQUEST_QUEUE_SIZE, e => logger.info(`producer REQUEST_QUEUE_SIZE ${e.payload.broker} size ${e.queueSize}`)); +*/ -// const removeListeners = {} -// const { FETCH_START } = consumer.events; -// removeListeners[FETCH_START] = consumer.on(FETCH_START, e => logger.info(`consumer FETCH_START`)); -// const { FETCH } = consumer.events; -// removeListeners[FETCH] = consumer.on(FETCH, e => logger.info(`consumer FETCH numberOfBatches ${e.payload.numberOfBatches} duration ${e.payload.duration}`)); -// const { START_BATCH_PROCESS } = consumer.events; -// removeListeners[START_BATCH_PROCESS] = consumer.on(START_BATCH_PROCESS, e => logger.info(`consumer START_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); -// const { END_BATCH_PROCESS } = consumer.events; -// removeListeners[END_BATCH_PROCESS] = consumer.on(END_BATCH_PROCESS, e => logger.info(`consumer END_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); -// const { COMMIT_OFFSETS } = consumer.events; -// removeListeners[COMMIT_OFFSETS] = consumer.on(COMMIT_OFFSETS, e => logger.info(`consumer COMMIT_OFFSETS topics ${e.payload.topics}`)); +/* + //consumer event instrumentation to debug + const removeListeners = {} + const { FETCH_START } = consumer.events; + removeListeners[FETCH_START] = consumer.on(FETCH_START, e => logger.info(`consumer FETCH_START`)); + const { FETCH } = consumer.events; + removeListeners[FETCH] = consumer.on(FETCH, e => logger.info(`consumer FETCH numberOfBatches ${e.payload.numberOfBatches} duration ${e.payload.duration}`)); + const { START_BATCH_PROCESS } = consumer.events; + removeListeners[START_BATCH_PROCESS] = consumer.on(START_BATCH_PROCESS, e => logger.info(`consumer START_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); + const { END_BATCH_PROCESS } = consumer.events; + removeListeners[END_BATCH_PROCESS] = consumer.on(END_BATCH_PROCESS, e => logger.info(`consumer END_BATCH_PROCESS topic ${e.payload.topic} batchSize ${e.payload.batchSize}`)); + const { COMMIT_OFFSETS } = consumer.events; + removeListeners[COMMIT_OFFSETS] = consumer.on(COMMIT_OFFSETS, e => logger.info(`consumer COMMIT_OFFSETS topics ${e.payload.topics}`)); +*/ const messageProcessor = new JsInvokeMessageProcessor(new KafkaProducer()); await consumer.connect(); From 2cba1e2f16e9a1a438d2e4bf7af0afa8968fbae5 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 28 May 2021 18:07:31 +0300 Subject: [PATCH 014/231] js-executor reduced log severity to debug --- msa/js-executor/queue/kafkaTemplate.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index 79d87c17b4..f467d0813b 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -75,9 +75,7 @@ function sendLoopWithLinger() { function sendMessagesAsBatch() { if (batchMessages.length > 0) { - if (batchMessages.length > 1) { - logger.info('sendMessagesAsBatch, length: [%s]', batchMessages.length); - } + logger.debug('sendMessagesAsBatch, length: [%s]', batchMessages.length); const messagesToSend = batchMessages; batchMessages = []; producer.sendBatch({ From a4e28ad94533610c4de0f855bad75dcd4460ac74 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 1 Jun 2021 16:00:32 +0300 Subject: [PATCH 015/231] js-executor fixed promises for each message for Kafka batches --- .../api/jsInvokeMessageProcessor.js | 24 +++++++------- msa/js-executor/queue/kafkaTemplate.js | 33 ++++++++++++------- 2 files changed, 33 insertions(+), 24 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index dec935c8ad..1aabd216d2 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -180,19 +180,17 @@ JsInvokeMessageProcessor.prototype.sendResponse = function (requestId, responseT var remoteResponse = createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse); var rawResponse = Buffer.from(JSON.stringify(remoteResponse), 'utf8'); logger.debug('[%s] Sending response to queue, scriptId: [%s]', requestId, scriptId); - this.producer.send(responseTopic, scriptId, rawResponse, headers); -//TODO put error msg for other queues implementation except Kafka -// .then( -// () => { -// logger.info('[%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); -// logger.error(err.stack); -// } -// } -// ); + 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); + logger.error(err.stack); + } + } + ); } JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scriptBody) { diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index f467d0813b..1fac350976 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -36,11 +36,12 @@ let producer; const configEntries = []; let batchMessages = []; +let batchResolvers = []; let sendLoopInstance; function KafkaProducer() { this.send = (responseTopic, scriptId, rawResponse, headers) => { - logger.debug('Pending queue response, scriptId: [%s]', scriptId); + logger.debug('Pending queue response, scriptId: [%s]', scriptId); const message = { topic: responseTopic, messages: [{ @@ -50,17 +51,22 @@ function KafkaProducer() { }] }; - pushMessageToSendLater(message); - return {}; + return pushMessageToSendLater(message); } } function pushMessageToSendLater(message) { + let resolver; + const promise = new Promise((resolve, reject) => { + resolver = resolve; + }); batchMessages.push(message); + batchResolvers.push(resolver); if (batchMessages.length >= maxBatchSize) { sendMessagesAsBatch(); sendLoopWithLinger(); //reset loop function and reschedule new linger } + return promise; } function sendLoopWithLinger() { @@ -77,22 +83,27 @@ function sendMessagesAsBatch() { if (batchMessages.length > 0) { logger.debug('sendMessagesAsBatch, length: [%s]', batchMessages.length); const messagesToSend = batchMessages; + const resolvers = batchResolvers; batchMessages = []; + batchResolvers = []; producer.sendBatch({ topicMessages: messagesToSend, acks: acks, compression: compressionType }).then( - () => { - logger.debug('Response sent to kafka, length: [%s]', messagesToSend.length); - }, - (err) => { - logger.error('Failed to send kafka, length: [%s], pending to reprocess msgs', messagesToSend.length); - batchMessages = messagesToSend.concat(batchMessages); - logger.error(err.stack); + () => { + logger.debug('Response batch sent to kafka, length: [%s]', messagesToSend.length); + for (let i = 0; i < promisesToSend.length; i++) { + resolvers[i](); } + }, + (err) => { + logger.error('Failed batch send to kafka, length: [%s], pending to reprocess msgs', messagesToSend.length); + logger.error(err.stack); + batchMessages = messagesToSend.concat(batchMessages); + batchResolvers = resolvers.concat(batchResolvers); //promises will never be rejected. Will retry forever + } ); - } } From 1d9fc4a322f8067b6e35bc96e8ff28eb13f2ca8e Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 1 Jun 2021 18:01:21 +0300 Subject: [PATCH 016/231] js-executor: typo fix --- msa/js-executor/queue/kafkaTemplate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index 1fac350976..ad243aacdd 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -93,7 +93,7 @@ function sendMessagesAsBatch() { }).then( () => { logger.debug('Response batch sent to kafka, length: [%s]', messagesToSend.length); - for (let i = 0; i < promisesToSend.length; i++) { + for (let i = 0; i < resolvers.length; i++) { resolvers[i](); } }, From 41391dbef8bbfbf1c0f56165dd41277dc45d23fe Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 1 Jun 2021 18:06:10 +0300 Subject: [PATCH 017/231] js-executor: code format --- .../api/jsInvokeMessageProcessor.js | 56 +++++++++---------- msa/js-executor/queue/kafkaTemplate.js | 46 +++++++-------- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index 1aabd216d2..bef70936ec 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -43,7 +43,7 @@ function JsInvokeMessageProcessor(producer) { this.lastStatTime = performance.now(); } -JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { +JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function (message) { var tStart = performance.now(); let requestId; let responseTopic; @@ -78,13 +78,13 @@ JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { var tFinish = performance.now(); var tTook = tFinish - tStart; - if ( tTook > slowQueryLogMs ) { + if (tTook > slowQueryLogMs) { let functionName; if (request.invokeRequest) { try { buf = Buffer.from(request.invokeRequest['functionName']); functionName = buf.toString('utf8'); - } catch (err){ + } catch (err) { logger.error('[%s] Failed to read functionName from message header: %s', requestId, err.message); logger.error(err.stack); } @@ -97,7 +97,7 @@ JsInvokeMessageProcessor.prototype.onJsInvokeMessage = function(message) { } -JsInvokeMessageProcessor.prototype.processCompileRequest = function(requestId, responseTopic, headers, compileRequest) { +JsInvokeMessageProcessor.prototype.processCompileRequest = function (requestId, responseTopic, headers, compileRequest) { var scriptId = getScriptId(compileRequest); logger.debug('[%s] Processing compile request, scriptId: [%s]', requestId, scriptId); @@ -116,7 +116,7 @@ JsInvokeMessageProcessor.prototype.processCompileRequest = function(requestId, r ); } -JsInvokeMessageProcessor.prototype.processInvokeRequest = function(requestId, responseTopic, headers, invokeRequest) { +JsInvokeMessageProcessor.prototype.processInvokeRequest = function (requestId, responseTopic, headers, invokeRequest) { var scriptId = getScriptId(invokeRequest); logger.debug('[%s] Processing invoke request, scriptId: [%s]', requestId, scriptId); this.executedScriptsCounter++; @@ -160,7 +160,7 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function(requestId, re ); } -JsInvokeMessageProcessor.prototype.processReleaseRequest = function(requestId, responseTopic, headers, releaseRequest) { +JsInvokeMessageProcessor.prototype.processReleaseRequest = function (requestId, responseTopic, headers, releaseRequest) { var scriptId = getScriptId(releaseRequest); logger.debug('[%s] Processing release request, scriptId: [%s]', requestId, scriptId); if (this.scriptMap.has(scriptId)) { @@ -193,9 +193,9 @@ JsInvokeMessageProcessor.prototype.sendResponse = function (requestId, responseT ); } -JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scriptBody) { +JsInvokeMessageProcessor.prototype.getOrCompileScript = function (scriptId, scriptBody) { var self = this; - return new Promise(function(resolve, reject) { + return new Promise(function (resolve, reject) { if (self.scriptMap.has(scriptId)) { resolve(self.scriptMap.get(scriptId)); } else { @@ -212,7 +212,7 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function(scriptId, scrip }); } -JsInvokeMessageProcessor.prototype.cacheScript = function(scriptId, script) { +JsInvokeMessageProcessor.prototype.cacheScript = function (scriptId, script) { if (!this.scriptMap.has(scriptId)) { this.scriptIds.push(scriptId); while (this.scriptIds.length > maxActiveScripts) { @@ -229,40 +229,40 @@ JsInvokeMessageProcessor.prototype.cacheScript = function(scriptId, script) { function createRemoteResponse(requestId, compileResponse, invokeResponse, releaseResponse) { const requestIdBits = Utils.UUIDToBits(requestId); return { - requestIdMSB: requestIdBits[0], - requestIdLSB: requestIdBits[1], - compileResponse: compileResponse, - invokeResponse: invokeResponse, - releaseResponse: releaseResponse + requestIdMSB: requestIdBits[0], + requestIdLSB: requestIdBits[1], + compileResponse: compileResponse, + invokeResponse: invokeResponse, + releaseResponse: releaseResponse }; } function createCompileResponse(scriptId, success, errorCode, err) { const scriptIdBits = Utils.UUIDToBits(scriptId); - return { - errorCode: errorCode, - success: success, - errorDetails: parseJsErrorDetails(err), - scriptIdMSB: scriptIdBits[0], - scriptIdLSB: scriptIdBits[1] + return { + errorCode: errorCode, + success: success, + errorDetails: parseJsErrorDetails(err), + scriptIdMSB: scriptIdBits[0], + scriptIdLSB: scriptIdBits[1] }; } function createInvokeResponse(result, success, errorCode, err) { - return { - errorCode: errorCode, - success: success, - errorDetails: parseJsErrorDetails(err), - result: result + return { + errorCode: errorCode, + success: success, + errorDetails: parseJsErrorDetails(err), + result: result }; } function createReleaseResponse(scriptId, success) { const scriptIdBits = Utils.UUIDToBits(scriptId); return { - success: success, - scriptIdMSB: scriptIdBits[0], - scriptIdLSB: scriptIdBits[1] + success: success, + scriptIdMSB: scriptIdBits[0], + scriptIdLSB: scriptIdBits[1] }; } diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index ad243aacdd..7fce429d23 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -45,9 +45,9 @@ function KafkaProducer() { const message = { topic: responseTopic, messages: [{ - key: scriptId, - value: rawResponse, - headers: headers.data + key: scriptId, + value: rawResponse, + headers: headers.data }] }; @@ -70,28 +70,28 @@ function pushMessageToSendLater(message) { } function sendLoopWithLinger() { - if (sendLoopInstance) { + if (sendLoopInstance) { logger.debug("Clear sendLoop scheduler. Starting new send loop with linger [%s]", linger); clearInterval(sendLoopInstance); - } else { + } else { logger.debug("Starting new send loop with linger [%s]", linger) - } - sendLoopInstance = setInterval(sendMessagesAsBatch, linger); + } + sendLoopInstance = setInterval(sendMessagesAsBatch, linger); } function sendMessagesAsBatch() { - if (batchMessages.length > 0) { - logger.debug('sendMessagesAsBatch, length: [%s]', batchMessages.length); - const messagesToSend = batchMessages; - const resolvers = batchResolvers; - batchMessages = []; - batchResolvers = []; - producer.sendBatch({ - topicMessages: messagesToSend, - acks: acks, - compression: compressionType - }).then( - () => { + if (batchMessages.length > 0) { + logger.debug('sendMessagesAsBatch, length: [%s]', batchMessages.length); + const messagesToSend = batchMessages; + const resolvers = batchResolvers; + batchMessages = []; + batchResolvers = []; + producer.sendBatch({ + topicMessages: messagesToSend, + acks: acks, + compression: compressionType + }).then( + () => { logger.debug('Response batch sent to kafka, length: [%s]', messagesToSend.length); for (let i = 0; i < resolvers.length; i++) { resolvers[i](); @@ -103,8 +103,8 @@ function sendMessagesAsBatch() { batchMessages = messagesToSend.concat(batchMessages); batchResolvers = resolvers.concat(batchResolvers); //promises will never be rejected. Will retry forever } - ); - } + ); + } } (async () => { @@ -120,8 +120,8 @@ function sendMessagesAsBatch() { let kafkaConfig = { brokers: kafkaBootstrapServers.split(','), - logLevel: logLevel.INFO, - logCreator: KafkaJsWinstonLogCreator + logLevel: logLevel.INFO, + logCreator: KafkaJsWinstonLogCreator }; if (kafkaClientId) { From ab5f1b5b639236fb7528ff9544425e2310c33dbb Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 2 Jun 2021 10:41:24 +0300 Subject: [PATCH 018/231] js-executor: ScriptMap optimize work --- msa/js-executor/api/jsInvokeMessageProcessor.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index bef70936ec..aa8f5d2db0 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -196,8 +196,9 @@ JsInvokeMessageProcessor.prototype.sendResponse = function (requestId, responseT JsInvokeMessageProcessor.prototype.getOrCompileScript = function (scriptId, scriptBody) { var self = this; return new Promise(function (resolve, reject) { - if (self.scriptMap.has(scriptId)) { - resolve(self.scriptMap.get(scriptId)); + const script = self.scriptMap.get(scriptId); + if (script !== undefined) { + resolve(script); } else { self.executor.compileScript(scriptBody).then( (script) => { From ff7fa6237f1f342357995c32488f542c984583d9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 2 Jun 2021 11:05:32 +0300 Subject: [PATCH 019/231] js-executor: zero check and code cleanup --- msa/js-executor/api/jsInvokeMessageProcessor.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.js b/msa/js-executor/api/jsInvokeMessageProcessor.js index aa8f5d2db0..5257a03168 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.js +++ b/msa/js-executor/api/jsInvokeMessageProcessor.js @@ -121,9 +121,9 @@ JsInvokeMessageProcessor.prototype.processInvokeRequest = function (requestId, r logger.debug('[%s] Processing invoke request, scriptId: [%s]', requestId, scriptId); this.executedScriptsCounter++; if (this.executedScriptsCounter % statFrequency == 0) { - var nowMs = performance.now(); - var msSinceLastStat = nowMs - this.lastStatTime; - var requestsPerSec = statFrequency / msSinceLastStat * 1000; //msSinceLastStat can't be zero in the real world + const nowMs = performance.now(); + const msSinceLastStat = nowMs - this.lastStatTime; + const requestsPerSec = msSinceLastStat == 0 ? statFrequency : statFrequency / msSinceLastStat * 1000; this.lastStatTime = nowMs; logger.info('STAT[%s]: requests [%s], took [%s]ms, request/s [%s]', this.executedScriptsCounter, statFrequency, msSinceLastStat, requestsPerSec); } @@ -197,13 +197,13 @@ JsInvokeMessageProcessor.prototype.getOrCompileScript = function (scriptId, scri var self = this; return new Promise(function (resolve, reject) { const script = self.scriptMap.get(scriptId); - if (script !== undefined) { + if (script) { resolve(script); } else { self.executor.compileScript(scriptBody).then( - (script) => { - self.cacheScript(scriptId, script); - resolve(script); + (compiledScript) => { + self.cacheScript(scriptId, compiledScript); + resolve(compiledScript); }, (err) => { reject(err); From 083dc38cf8f587dd10f386095b1ce3cbde826fc6 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 3 Jun 2021 19:32:25 +0300 Subject: [PATCH 020/231] Refactoring of the ttl cleanup services --- .../service/ttl/AbstractCleanUpService.java | 44 ++++--------------- .../{alarms => }/AlarmsCleanUpService.java | 3 +- .../{edge => }/EdgeEventsCleanUpService.java | 34 +++++++------- .../{events => }/EventsCleanUpService.java | 34 +++++++------- ...ice.java => TimeseriesCleanUpService.java} | 25 ++++++----- .../PsqlTimeseriesCleanUpService.java | 44 ------------------- .../TimescaleTimeseriesCleanUpService.java | 36 --------------- .../server/dao/edge/EdgeService.java | 2 + .../server/dao/event/EventService.java | 2 + .../dao/timeseries/TimeseriesService.java | 2 + .../thingsboard/server/dao/edge/EdgeDao.java | 7 +++ .../server/dao/edge/EdgeServiceImpl.java | 5 +++ .../server/dao/event/BaseEventService.java | 5 +++ .../server/dao/event/EventDao.java | 7 +++ ...paAbstractDaoListeningExecutorService.java | 22 ++++++++++ .../server/dao/sql/edge/JpaEdgeDao.java | 18 ++++++++ .../server/dao/sql/event/JpaBaseEventDao.java | 23 +++++++++- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 19 ++++++++ .../dao/sqlts/hsql/JpaHsqlTimeseriesDao.java | 5 +++ .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 26 +++++++++++ .../timescale/TimescaleTimeseriesDao.java | 9 ++++ .../dao/timeseries/BaseTimeseriesService.java | 5 +++ .../CassandraBaseTimeseriesDao.java | 5 +++ .../server/dao/timeseries/TimeseriesDao.java | 2 + 24 files changed, 219 insertions(+), 165 deletions(-) rename application/src/main/java/org/thingsboard/server/service/ttl/{alarms => }/AlarmsCleanUpService.java (99%) rename application/src/main/java/org/thingsboard/server/service/ttl/{edge => }/EdgeEventsCleanUpService.java (63%) rename application/src/main/java/org/thingsboard/server/service/ttl/{events => }/EventsCleanUpService.java (63%) rename application/src/main/java/org/thingsboard/server/service/ttl/{timeseries/AbstractTimeseriesCleanUpService.java => TimeseriesCleanUpService.java} (65%) delete mode 100644 application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java index 05799fc643..d0ed543b76 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AbstractCleanUpService.java @@ -15,8 +15,13 @@ */ package org.thingsboard.server.service.ttl; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.queue.discovery.PartitionService; import java.sql.Connection; import java.sql.DriverManager; @@ -27,43 +32,12 @@ import java.sql.Statement; @Slf4j +@RequiredArgsConstructor public abstract class AbstractCleanUpService { - @Value("${spring.datasource.url}") - protected String dbUrl; + private final PartitionService partitionService; - @Value("${spring.datasource.username}") - protected String dbUserName; - - @Value("${spring.datasource.password}") - protected String dbPassword; - - protected long executeQuery(Connection conn, String query) throws SQLException { - try (Statement statement = conn.createStatement(); ResultSet resultSet = statement.executeQuery(query)) { - if (log.isDebugEnabled()) { - getWarnings(statement); - } - resultSet.next(); - return resultSet.getLong(1); - } - } - - protected void getWarnings(Statement statement) throws SQLException { - SQLWarning warnings = statement.getWarnings(); - if (warnings != null) { - log.debug("{}", warnings.getMessage()); - SQLWarning nextWarning = warnings.getNextWarning(); - while (nextWarning != null) { - log.debug("{}", nextWarning.getMessage()); - nextWarning = nextWarning.getNextWarning(); - } - } + protected boolean isSystemTenantPartitionMine(){ + return partitionService.resolve(ServiceType.TB_CORE, TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID).isMyPartition(); } - - protected abstract void doCleanUp(Connection connection) throws SQLException; - - protected Connection getConnection() throws SQLException { - return DriverManager.getConnection(dbUrl, dbUserName, dbPassword); - } - } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/alarms/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java similarity index 99% rename from application/src/main/java/org/thingsboard/server/service/ttl/alarms/AlarmsCleanUpService.java rename to application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java index 3b76a6cbca..051a6c92b2 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/alarms/AlarmsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ttl.alarms; +package org.thingsboard.server.service.ttl; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -52,6 +52,7 @@ import java.util.concurrent.TimeUnit; @Slf4j @RequiredArgsConstructor public class AlarmsCleanUpService { + @Value("${sql.ttl.alarms.removal_batch_size}") private Integer removalBatchSize; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java similarity index 63% rename from application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java rename to application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java index e93a82c7eb..7aa8881bed 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/edge/EdgeEventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java @@ -13,20 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ttl.edge; +package org.thingsboard.server.service.ttl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.ttl.AbstractCleanUpService; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - -@PsqlDao +@TbCoreComponent @Slf4j @Service public class EdgeEventsCleanUpService extends AbstractCleanUpService { @@ -37,20 +35,20 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService { @Value("${sql.ttl.edge_events.enabled}") private boolean ttlTaskExecutionEnabled; + private final EdgeService edgeService; + + public EdgeEventsCleanUpService(PartitionService partitionService, EdgeService edgeService) { + super(partitionService); + this.edgeService = edgeService; + } + @Scheduled(initialDelayString = "${sql.ttl.edge_events.execution_interval_ms}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}") public void cleanUp() { - if (ttlTaskExecutionEnabled) { - try (Connection conn = getConnection()) { - doCleanUp(conn); - } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); - } + if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { + log.info("Going to cleanup old edge events using ttl: {}s", ttl); + long totalEdgeEventsRemoved = edgeService.cleanupEvents(ttl); + log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved); } } - @Override - protected void doCleanUp(Connection connection) throws SQLException { - long totalEdgeEventsRemoved = executeQuery(connection, "call cleanup_edge_events_by_ttl(" + ttl + ", 0);"); - log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java similarity index 63% rename from application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java rename to application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java index 407c88261f..b244e1b8fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/events/EventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java @@ -13,20 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ttl.events; +package org.thingsboard.server.service.ttl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.util.PsqlDao; +import org.thingsboard.server.dao.event.EventService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.ttl.AbstractCleanUpService; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - -@PsqlDao +@TbCoreComponent @Slf4j @Service public class EventsCleanUpService extends AbstractCleanUpService { @@ -40,20 +38,20 @@ public class EventsCleanUpService extends AbstractCleanUpService { @Value("${sql.ttl.events.enabled}") private boolean ttlTaskExecutionEnabled; + private final EventService eventService; + + public EventsCleanUpService(PartitionService partitionService, EventService eventService) { + super(partitionService); + this.eventService = eventService; + } + @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}") public void cleanUp() { - if (ttlTaskExecutionEnabled) { - try (Connection conn = getConnection()) { - doCleanUp(conn); - } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); - } + if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { + log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugTtl, ttl); + long totalEventsRemoved = eventService.cleanupEvents(ttl, debugTtl); + log.info("Total events removed by TTL: [{}]", totalEventsRemoved); } } - @Override - protected void doCleanUp(Connection connection) throws SQLException { - long totalEventsRemoved = executeQuery(connection, "call cleanup_events_by_ttl(" + ttl + ", " + debugTtl + ", 0);"); - log.info("Total events removed by TTL: [{}]", totalEventsRemoved); - } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/AbstractTimeseriesCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java similarity index 65% rename from application/src/main/java/org/thingsboard/server/service/ttl/timeseries/AbstractTimeseriesCleanUpService.java rename to application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java index ee2d437a22..7094b34e62 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/AbstractTimeseriesCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java @@ -13,19 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.ttl.timeseries; +package org.thingsboard.server.service.ttl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.service.ttl.AbstractCleanUpService; -import java.sql.Connection; -import java.sql.DriverManager; -import java.sql.SQLException; - @Slf4j -public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpService { +public class TimeseriesCleanUpService extends AbstractCleanUpService { @Value("${sql.ttl.ts.ts_key_value_ttl}") protected long systemTtl; @@ -33,14 +31,17 @@ public abstract class AbstractTimeseriesCleanUpService extends AbstractCleanUpSe @Value("${sql.ttl.ts.enabled}") private boolean ttlTaskExecutionEnabled; + private final TimeseriesService timeseriesService; + + public TimeseriesCleanUpService(PartitionService partitionService, TimeseriesService timeseriesService) { + super(partitionService); + this.timeseriesService = timeseriesService; + } + @Scheduled(initialDelayString = "${sql.ttl.ts.execution_interval_ms}", fixedDelayString = "${sql.ttl.ts.execution_interval_ms}") public void cleanUp() { - if (ttlTaskExecutionEnabled) { - try (Connection conn = getConnection()) { - doCleanUp(conn); - } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); - } + if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { + timeseriesService.cleanup(systemTtl); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java deleted file mode 100644 index 3197f0cbb0..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/PsqlTimeseriesCleanUpService.java +++ /dev/null @@ -1,44 +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.service.ttl.timeseries; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.PsqlDao; -import org.thingsboard.server.dao.util.SqlTsDao; - -import java.sql.Connection; -import java.sql.SQLException; - -@SqlTsDao -@PsqlDao -@Service -@Slf4j -public class PsqlTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService { - - @Value("${sql.postgres.ts_key_value_partitioning}") - private String partitionType; - - @Override - protected void doCleanUp(Connection connection) throws SQLException { - long totalPartitionsRemoved = executeQuery(connection, "call drop_partitions_by_max_ttl('" + partitionType + "'," + systemTtl + ", 0);"); - log.info("Total partitions removed by TTL: [{}]", totalPartitionsRemoved); - long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);"); - log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved); - } -} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java deleted file mode 100644 index 0ed61ef97c..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/ttl/timeseries/TimescaleTimeseriesCleanUpService.java +++ /dev/null @@ -1,36 +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.service.ttl.timeseries; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.util.TimescaleDBTsDao; - -import java.sql.Connection; -import java.sql.SQLException; - -@TimescaleDBTsDao -@Service -@Slf4j -public class TimescaleTimeseriesCleanUpService extends AbstractTimeseriesCleanUpService { - - @Override - protected void doCleanUp(Connection connection) throws SQLException { - long totalEntitiesTelemetryRemoved = executeQuery(connection, "call cleanup_timeseries_by_ttl('" + ModelConstants.NULL_UUID + "'," + systemTtl + ", 0);"); - log.info("Total telemetry removed stats by TTL for entities: [{}]", totalEntitiesTelemetryRemoved); - } -} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index a7b9145c01..8567077eb1 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -93,4 +93,6 @@ public interface EdgeService { Object activateInstance(String licenseSecret, String releaseDate); String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId); + + long cleanupEvents(long ttl); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index ea25568375..dce49afdb1 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -46,4 +46,6 @@ public interface EventService { void removeEvents(TenantId tenantId, EntityId entityId); + long cleanupEvents(long ttl, long debugTtl); + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index 6fcb5ca2ca..b1a2541fc7 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java @@ -52,4 +52,6 @@ public interface TimeseriesService { List findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId); List findAllKeysByEntityIds(TenantId tenantId, List entityIds); + + void cleanup(long systemTtl); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java index d254c5ee9a..eda1075cd6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java @@ -176,4 +176,11 @@ public interface EdgeDao extends Dao { * @return the list of rule chain objects */ ListenableFuture> findEdgesByTenantIdAndDashboardId(UUID tenantId, UUID dashboardId); + + /** + * Executes stored procedure to cleanup old edge events. + * @param ttl the ttl for edge events in seconds + * @return the number of deleted edge events + */ + long cleanupEvents(long ttl); } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 2ff7d94f43..d06ce8d722 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -627,6 +627,11 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic return result.toString(); } + @Override + public long cleanupEvents(long ttl) { + return edgeDao.cleanupEvents(ttl); + } + private List findEdgeRuleChains(TenantId tenantId, EdgeId edgeId) { List result = new ArrayList<>(); PageLink pageLink = new PageLink(DEFAULT_LIMIT); diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index 91bfa954fd..4cfd0a3ffa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -131,6 +131,11 @@ public class BaseEventService implements EventService { } while (eventPageData.hasNext()); } + @Override + public long cleanupEvents(long ttl, long debugTtl) { + return eventDao.cleanupEvents(ttl, debugTtl); + } + private DataValidator eventValidator = new DataValidator() { @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java index ba4e86c95e..d943937ca3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java @@ -102,4 +102,11 @@ public interface EventDao extends Dao { */ List findLatestEvents(UUID tenantId, EntityId entityId, String eventType, int limit); + /** + * Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events. + * @param otherEventsTtl the ttl for events in seconds + * @param debugEventsTtl the ttl for debug events in seconds + * @return the number of deleted events + */ + long cleanupEvents(long otherEventsTtl, long debugEventsTtl); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java index 4431356690..fcd679383f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDaoListeningExecutorService.java @@ -15,11 +15,33 @@ */ package org.thingsboard.server.dao.sql; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import javax.sql.DataSource; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; + +@Slf4j public abstract class JpaAbstractDaoListeningExecutorService { @Autowired protected JpaExecutorService service; + @Autowired + protected DataSource dataSource; + + protected void printWarnings(Statement statement) throws SQLException { + SQLWarning warnings = statement.getWarnings(); + if (warnings != null) { + log.debug("{}", warnings.getMessage()); + SQLWarning nextWarning = warnings.getNextWarning(); + while (nextWarning != null) { + log.debug("{}", nextWarning.getMessage()); + nextWarning = nextWarning.getNextWarning(); + } + } + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java index f17196fe93..8e7112e81d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java @@ -40,6 +40,9 @@ import org.thingsboard.server.dao.model.sql.EdgeInfoEntity; import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -194,6 +197,21 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao imple return transformFromRelationToEdge(tenantId, relations); } + @Override + public long cleanupEvents(long ttl) { + try { + CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_edge_events_by_ttl(?,?,?)}"); + stmt.setLong(1, ttl); + stmt.registerOutParameter(3, Types.BIGINT); + stmt.executeUpdate(); + printWarnings(stmt); + return stmt.getLong(3); + } catch (SQLException e) { + log.error("SQLException occurred during TTL task execution ", e); + return 0; + } + } + private ListenableFuture> transformFromRelationToEdge(UUID tenantId, ListenableFuture> relations) { return Futures.transformAsync(relations, input -> { List> edgeFutures = new ArrayList<>(input.size()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index 44848ec515..edaf95a29d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -27,7 +27,6 @@ import org.thingsboard.server.common.data.Event; import org.thingsboard.server.common.data.event.DebugEvent; import org.thingsboard.server.common.data.event.ErrorEventFilter; import org.thingsboard.server.common.data.event.EventFilter; -import org.thingsboard.server.common.data.event.EventType; import org.thingsboard.server.common.data.event.LifeCycleEventFilter; import org.thingsboard.server.common.data.event.StatisticsEventFilter; import org.thingsboard.server.common.data.id.EntityId; @@ -40,6 +39,12 @@ import org.thingsboard.server.dao.event.EventDao; import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; +import javax.sql.DataSource; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.Statement; +import java.sql.Types; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -256,6 +261,22 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen return DaoUtil.convertDataList(latest); } + @Override + public long cleanupEvents(long otherEventsTtl, long debugEventsTtl) { + try { + CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_events_by_ttl(?,?,?)}"); + stmt.setLong(1, otherEventsTtl); + stmt.setLong(2, debugEventsTtl); + stmt.registerOutParameter(3, Types.BIGINT); + stmt.executeUpdate(); + printWarnings(stmt); + return stmt.getLong(3); + } catch (SQLException e) { + log.error("SQLException occurred during TTL task execution ", e); + return 0; + } + } + public Optional save(EventEntity entity, boolean ifNotExists) { log.debug("Save event [{}] ", entity); if (entity.getTenantId() == null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index f6a6b56be5..abd8f15e77 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -25,9 +25,13 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import javax.annotation.Nullable; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -62,6 +66,21 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries @Value("${sql.ttl.ts.ts_key_value_ttl:0}") private long systemTtl; + public void cleanup(long systemTtl) { + try { + log.info("Going to cleanup old timeseries data using ttl: {}s", systemTtl); + CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_timeseries_by_ttl(?,?,?)}"); + stmt.setObject(1, ModelConstants.NULL_UUID); + stmt.setLong(2, systemTtl); + stmt.registerOutParameter(3, Types.BIGINT); + stmt.executeUpdate(); + printWarnings(stmt); + log.info("Total telemetry removed stats by TTL for entities: [{}]", stmt.getLong(3)); + } catch (SQLException e) { + log.error("SQLException occurred during TTL task execution ", e); + } + } + protected ListenableFuture> processFindAllAsync(TenantId tenantId, EntityId entityId, List queries) { List>> futures = queries .stream() diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java index c01d91ec61..c8241714f4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java @@ -54,4 +54,9 @@ public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); } + @Override + public void cleanup(long systemTtl) { + + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index 64c074fd40..594f77ae78 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -27,6 +27,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao; import org.thingsboard.server.dao.sqlts.insert.psql.PsqlPartitioningRepository; @@ -35,6 +36,9 @@ import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -62,6 +66,7 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa @Value("${sql.postgres.ts_key_value_partitioning:MONTHS}") private String partitioning; + @Override protected void init() { super.init(); @@ -93,6 +98,27 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); } + @Override + public void cleanup(long systemTtl) { + cleanupPartitions(systemTtl); + super.cleanup(systemTtl); + } + + private void cleanupPartitions(long systemTtl) { + try { + log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl); + CallableStatement stmt = dataSource.getConnection().prepareCall("{call drop_partitions_by_max_ttl(?,?,?)}"); + stmt.setObject(1, partitioning); + stmt.setLong(2, systemTtl); + stmt.registerOutParameter(3, Types.BIGINT); + stmt.executeUpdate(); + printWarnings(stmt); + log.info("Total partitions removed by TTL: [{}]", stmt.getLong(3)); + } catch (SQLException e) { + log.error("SQLException occurred during TTL task execution ", e); + } + } + private void savePartitionIfNotExist(long ts) { if (!tsFormat.equals(SqlTsPartitionDate.INDEFINITE) && ts >= 0) { LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 7f798ddb4b..31f3407fbf 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -34,6 +34,7 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; @@ -45,6 +46,9 @@ import org.thingsboard.server.dao.util.TimescaleDBTsDao; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; +import java.sql.CallableStatement; +import java.sql.SQLException; +import java.sql.Types; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.function.Function; @@ -156,6 +160,11 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } } + @Override + public void cleanup(long systemTtl) { + super.cleanup(systemTtl); + } + private ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { String strKey = query.getKey(); Integer keyId = getOrSaveKeyId(strKey); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 701c67a648..fb15af723a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -126,6 +126,11 @@ public class BaseTimeseriesService implements TimeseriesService { return timeseriesLatestDao.findAllKeysByEntityIds(tenantId, entityIds); } + @Override + public void cleanup(long systemTtl) { + timeseriesDao.cleanup(systemTtl); + } + @Override public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { validate(entityId); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 240d5a0b88..ce653e2e6e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -288,6 +288,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } } + @Override + public void cleanup(long systemTtl) { + //Cleanup by TTL is native for Cassandra + } + private ListenableFuture> findAllAsyncWithLimit(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { long minPartition = toPartitionTs(query.getStartTs()); long maxPartition = toPartitionTs(query.getEndTs()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java index 3b3eb4ee0a..e9af5f0b75 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java @@ -38,4 +38,6 @@ public interface TimeseriesDao { ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); + + void cleanup(long systemTtl); } From d1292a0d04596c3453514b2d973b38a469221ecc Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 7 Jun 2021 13:23:01 +0300 Subject: [PATCH 021/231] refactoring ttl cleanup services --- .../schema_update_psql_drop_partitions.sql | 174 +++++++++++++----- .../service/ttl/EdgeEventsCleanUpService.java | 4 +- .../service/ttl/EventsCleanUpService.java | 4 +- .../service/ttl/TimeseriesCleanUpService.java | 4 + .../server/dao/edge/EdgeService.java | 2 +- .../server/dao/event/EventService.java | 2 +- .../thingsboard/server/dao/edge/EdgeDao.java | 3 +- .../server/dao/edge/EdgeServiceImpl.java | 4 +- .../server/dao/event/BaseEventService.java | 4 +- .../server/dao/event/EventDao.java | 3 +- .../server/dao/sql/edge/JpaEdgeDao.java | 20 +- .../server/dao/sql/event/JpaBaseEventDao.java | 23 ++- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 16 +- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 17 +- dao/src/main/resources/sql/schema-ts-psql.sql | 174 +++++++++++++----- 15 files changed, 312 insertions(+), 142 deletions(-) diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql index fcc5c6f232..9d336e0330 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql @@ -18,17 +18,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar LANGUAGE plpgsql AS $$ DECLARE - max_tenant_ttl bigint; - max_customer_ttl bigint; - max_ttl bigint; - date timestamp; - partition_by_max_ttl_date varchar; - partition_month varchar; - partition_day varchar; - partition_year varchar; - partition varchar; - partition_to_delete varchar; - + max_tenant_ttl bigint; + max_customer_ttl bigint; + max_ttl bigint; + date timestamp; + partition_by_max_ttl_date varchar; + partition_by_max_ttl_month varchar; + partition_by_max_ttl_day varchar; + partition_by_max_ttl_year varchar; + partition varchar; + partition_year integer; + partition_month integer; + partition_day integer; BEGIN SELECT max(attribute_kv.long_v) @@ -45,53 +46,138 @@ BEGIN if max_ttl IS NOT NULL AND max_ttl > 0 THEN date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); + RAISE NOTICE 'Date by max ttl: %', date; RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; IF partition_by_max_ttl_date IS NOT NULL THEN CASE WHEN partition_type = 'DAYS' THEN - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); - partition_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); WHEN partition_type = 'MONTHS' THEN - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); ELSE - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); END CASE; - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - LOOP - IF partition != partition_by_max_ttl_date THEN - IF partition_year IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 3)::integer < partition_year::integer THEN - partition_to_delete := partition; - ELSE - IF partition_month IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 4)::integer < partition_month::integer THEN - partition_to_delete := partition; + IF partition_by_max_ttl_year IS NULL THEN + RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; + ELSE + IF partition_type = 'YEARS' THEN + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END LOOP; + ELSE + IF partition_type = 'MONTHS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; ELSE - IF partition_day IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 5)::integer < partition_day::integer THEN - partition_to_delete := partition; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_year = partition_by_max_ttl_year::integer THEN + IF partition_month >= partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; END IF; END IF; END IF; + END LOOP; + END IF; + ELSE + IF partition_type = 'DAYS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + IF partition_by_max_ttl_day IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_month > partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_month < partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_day := SPLIT_PART(partition, '_', 5)::integer; + IF partition_day >= partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_day < partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END LOOP; END IF; END IF; END IF; - IF partition_to_delete IS NOT NULL THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete; - EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete); - partition_to_delete := NULL; - deleted := deleted + 1; - END IF; END IF; - END LOOP; + END IF; + END IF; END IF; END IF; END @@ -107,8 +193,6 @@ BEGIN partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); WHEN partition_type = 'YEARS' THEN partition := 'ts_kv_' || to_char(date, 'yyyy'); - WHEN partition_type = 'INDEFINITE' THEN - partition := NULL; ELSE partition := NULL; END CASE; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java index 7aa8881bed..3cdd1f71bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java @@ -45,9 +45,7 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService { @Scheduled(initialDelayString = "${sql.ttl.edge_events.execution_interval_ms}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}") public void cleanUp() { if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { - log.info("Going to cleanup old edge events using ttl: {}s", ttl); - long totalEdgeEventsRemoved = edgeService.cleanupEvents(ttl); - log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved); + edgeService.cleanupEvents(ttl); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java index b244e1b8fd..a51910b7ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java @@ -48,9 +48,7 @@ public class EventsCleanUpService extends AbstractCleanUpService { @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}") public void cleanUp() { if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { - log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugTtl, ttl); - long totalEventsRemoved = eventService.cleanupEvents(ttl, debugTtl); - log.info("Total events removed by TTL: [{}]", totalEventsRemoved); + eventService.cleanupEvents(ttl, debugTtl); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java index 7094b34e62..55c746b580 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java @@ -18,11 +18,15 @@ package org.thingsboard.server.service.ttl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.ttl.AbstractCleanUpService; +@TbCoreComponent @Slf4j +@Service public class TimeseriesCleanUpService extends AbstractCleanUpService { @Value("${sql.ttl.ts.ts_key_value_ttl}") diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index 8567077eb1..53472fd261 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -94,5 +94,5 @@ public interface EdgeService { String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId); - long cleanupEvents(long ttl); + void cleanupEvents(long ttl); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index dce49afdb1..db1c77697e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -46,6 +46,6 @@ public interface EventService { void removeEvents(TenantId tenantId, EntityId entityId); - long cleanupEvents(long ttl, long debugTtl); + void cleanupEvents(long ttl, long debugTtl); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java index eda1075cd6..60ef98a801 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java @@ -180,7 +180,6 @@ public interface EdgeDao extends Dao { /** * Executes stored procedure to cleanup old edge events. * @param ttl the ttl for edge events in seconds - * @return the number of deleted edge events */ - long cleanupEvents(long ttl); + void cleanupEvents(long ttl); } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index d06ce8d722..0089853969 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -628,8 +628,8 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic } @Override - public long cleanupEvents(long ttl) { - return edgeDao.cleanupEvents(ttl); + public void cleanupEvents(long ttl) { + edgeDao.cleanupEvents(ttl); } private List findEdgeRuleChains(TenantId tenantId, EdgeId edgeId) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index 4cfd0a3ffa..2785df90df 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -132,8 +132,8 @@ public class BaseEventService implements EventService { } @Override - public long cleanupEvents(long ttl, long debugTtl) { - return eventDao.cleanupEvents(ttl, debugTtl); + public void cleanupEvents(long ttl, long debugTtl) { + eventDao.cleanupEvents(ttl, debugTtl); } private DataValidator eventValidator = diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java index d943937ca3..ceacbffd50 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java @@ -106,7 +106,6 @@ public interface EventDao extends Dao { * Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events. * @param otherEventsTtl the ttl for events in seconds * @param debugEventsTtl the ttl for debug events in seconds - * @return the number of deleted events */ - long cleanupEvents(long otherEventsTtl, long debugEventsTtl); + void cleanupEvents(long otherEventsTtl, long debugEventsTtl); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java index 8e7112e81d..ec6d730230 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java @@ -40,9 +40,9 @@ import org.thingsboard.server.dao.model.sql.EdgeInfoEntity; import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -198,17 +198,19 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao imple } @Override - public long cleanupEvents(long ttl) { + public void cleanupEvents(long ttl) { try { - CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_edge_events_by_ttl(?,?,?)}"); + log.info("Going to cleanup old edge events using ttl: {}s", ttl); + PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_edge_events_by_ttl(?,?)"); stmt.setLong(1, ttl); - stmt.registerOutParameter(3, Types.BIGINT); - stmt.executeUpdate(); + stmt.setLong(2, 0); + stmt.execute(); printWarnings(stmt); - return stmt.getLong(3); + ResultSet resultSet = stmt.getResultSet(); + resultSet.next(); + log.info("Total edge events removed by TTL: [{}]", resultSet.getLong(1)); } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); - return 0; + log.error("SQLException occurred during edge events TTL task execution ", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index edaf95a29d..cde7d75d32 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -39,12 +39,9 @@ import org.thingsboard.server.dao.event.EventDao; import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; -import javax.sql.DataSource; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLWarning; -import java.sql.Statement; -import java.sql.Types; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -262,18 +259,20 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen } @Override - public long cleanupEvents(long otherEventsTtl, long debugEventsTtl) { + public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { try { - CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_events_by_ttl(?,?,?)}"); + log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugEventsTtl, otherEventsTtl); + PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_events_by_ttl(?,?,?)"); stmt.setLong(1, otherEventsTtl); stmt.setLong(2, debugEventsTtl); - stmt.registerOutParameter(3, Types.BIGINT); - stmt.executeUpdate(); + stmt.setLong(3, 0); + stmt.execute(); printWarnings(stmt); - return stmt.getLong(3); + ResultSet resultSet = stmt.getResultSet(); + resultSet.next(); + log.info("Total events removed by TTL: [{}]", resultSet.getLong(1)); } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); - return 0; + log.error("SQLException occurred during events TTL task execution ", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index abd8f15e77..f1636740c3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -29,9 +29,9 @@ import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import javax.annotation.Nullable; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -69,15 +69,17 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries public void cleanup(long systemTtl) { try { log.info("Going to cleanup old timeseries data using ttl: {}s", systemTtl); - CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_timeseries_by_ttl(?,?,?)}"); + PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_timeseries_by_ttl(?,?,?)"); stmt.setObject(1, ModelConstants.NULL_UUID); stmt.setLong(2, systemTtl); - stmt.registerOutParameter(3, Types.BIGINT); - stmt.executeUpdate(); + stmt.setLong(3, 0); + stmt.execute(); printWarnings(stmt); - log.info("Total telemetry removed stats by TTL for entities: [{}]", stmt.getLong(3)); + ResultSet resultSet = stmt.getResultSet(); + resultSet.next(); + log.info("Total telemetry removed stats by TTL for entities: [{}]", resultSet.getLong(1)); } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); + log.error("SQLException occurred during timeseries TTL task execution ", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index 594f77ae78..9b18dcde7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -27,7 +27,6 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao; import org.thingsboard.server.dao.sqlts.insert.psql.PsqlPartitioningRepository; @@ -36,9 +35,9 @@ import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -107,13 +106,15 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa private void cleanupPartitions(long systemTtl) { try { log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl); - CallableStatement stmt = dataSource.getConnection().prepareCall("{call drop_partitions_by_max_ttl(?,?,?)}"); - stmt.setObject(1, partitioning); + PreparedStatement stmt = dataSource.getConnection().prepareStatement("call drop_partitions_by_max_ttl(?,?,?)"); + stmt.setString(1, partitioning); stmt.setLong(2, systemTtl); - stmt.registerOutParameter(3, Types.BIGINT); - stmt.executeUpdate(); + stmt.setLong(3, 0); + stmt.execute(); printWarnings(stmt); - log.info("Total partitions removed by TTL: [{}]", stmt.getLong(3)); + ResultSet resultSet = stmt.getResultSet(); + resultSet.next(); + log.info("Total partitions removed by TTL: [{}]", resultSet.getLong(1)); } catch (SQLException e) { log.error("SQLException occurred during TTL task execution ", e); } diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index 5683cc0a17..2744ff5a07 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -38,17 +38,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar LANGUAGE plpgsql AS $$ DECLARE - max_tenant_ttl bigint; - max_customer_ttl bigint; - max_ttl bigint; - date timestamp; - partition_by_max_ttl_date varchar; - partition_month varchar; - partition_day varchar; - partition_year varchar; - partition varchar; - partition_to_delete varchar; - + max_tenant_ttl bigint; + max_customer_ttl bigint; + max_ttl bigint; + date timestamp; + partition_by_max_ttl_date varchar; + partition_by_max_ttl_month varchar; + partition_by_max_ttl_day varchar; + partition_by_max_ttl_year varchar; + partition varchar; + partition_year integer; + partition_month integer; + partition_day integer; BEGIN SELECT max(attribute_kv.long_v) @@ -65,53 +66,138 @@ BEGIN if max_ttl IS NOT NULL AND max_ttl > 0 THEN date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); + RAISE NOTICE 'Date by max ttl: %', date; RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; IF partition_by_max_ttl_date IS NOT NULL THEN CASE WHEN partition_type = 'DAYS' THEN - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); - partition_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); WHEN partition_type = 'MONTHS' THEN - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); ELSE - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); END CASE; - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - LOOP - IF partition != partition_by_max_ttl_date THEN - IF partition_year IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 3)::integer < partition_year::integer THEN - partition_to_delete := partition; - ELSE - IF partition_month IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 4)::integer < partition_month::integer THEN - partition_to_delete := partition; + IF partition_by_max_ttl_year IS NULL THEN + RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; + ELSE + IF partition_type = 'YEARS' THEN + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END LOOP; + ELSE + IF partition_type = 'MONTHS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; ELSE - IF partition_day IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 5)::integer < partition_day::integer THEN - partition_to_delete := partition; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_year = partition_by_max_ttl_year::integer THEN + IF partition_month >= partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; END IF; END IF; END IF; + END LOOP; + END IF; + ELSE + IF partition_type = 'DAYS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + IF partition_by_max_ttl_day IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_month > partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_month < partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_day := SPLIT_PART(partition, '_', 5)::integer; + IF partition_day >= partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_day < partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END LOOP; END IF; END IF; END IF; - IF partition_to_delete IS NOT NULL THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete; - EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete); - partition_to_delete := NULL; - deleted := deleted + 1; - END IF; END IF; - END LOOP; + END IF; + END IF; END IF; END IF; END @@ -127,8 +213,6 @@ BEGIN partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); WHEN partition_type = 'YEARS' THEN partition := 'ts_kv_' || to_char(date, 'yyyy'); - WHEN partition_type = 'INDEFINITE' THEN - partition := NULL; ELSE partition := NULL; END CASE; From 6af34cf043da68943fd4aa26c608e548c3140776 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 7 Jun 2021 13:23:01 +0300 Subject: [PATCH 022/231] refactoring ttl cleanup services --- .../schema_update_psql_drop_partitions.sql | 174 +++++++++++++----- .../service/ttl/EdgeEventsCleanUpService.java | 4 +- .../service/ttl/EventsCleanUpService.java | 4 +- .../service/ttl/TimeseriesCleanUpService.java | 4 + .../server/dao/edge/EdgeService.java | 2 +- .../server/dao/event/EventService.java | 2 +- .../thingsboard/server/dao/edge/EdgeDao.java | 3 +- .../server/dao/edge/EdgeServiceImpl.java | 4 +- .../server/dao/event/BaseEventService.java | 4 +- .../server/dao/event/EventDao.java | 3 +- .../server/dao/sql/edge/JpaEdgeDao.java | 20 +- .../server/dao/sql/event/JpaBaseEventDao.java | 23 ++- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 16 +- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 17 +- dao/src/main/resources/sql/schema-ts-psql.sql | 174 +++++++++++++----- 15 files changed, 312 insertions(+), 142 deletions(-) diff --git a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql index fcc5c6f232..9d336e0330 100644 --- a/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql +++ b/application/src/main/data/upgrade/2.4.3/schema_update_psql_drop_partitions.sql @@ -18,17 +18,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar LANGUAGE plpgsql AS $$ DECLARE - max_tenant_ttl bigint; - max_customer_ttl bigint; - max_ttl bigint; - date timestamp; - partition_by_max_ttl_date varchar; - partition_month varchar; - partition_day varchar; - partition_year varchar; - partition varchar; - partition_to_delete varchar; - + max_tenant_ttl bigint; + max_customer_ttl bigint; + max_ttl bigint; + date timestamp; + partition_by_max_ttl_date varchar; + partition_by_max_ttl_month varchar; + partition_by_max_ttl_day varchar; + partition_by_max_ttl_year varchar; + partition varchar; + partition_year integer; + partition_month integer; + partition_day integer; BEGIN SELECT max(attribute_kv.long_v) @@ -45,53 +46,138 @@ BEGIN if max_ttl IS NOT NULL AND max_ttl > 0 THEN date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); + RAISE NOTICE 'Date by max ttl: %', date; RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; IF partition_by_max_ttl_date IS NOT NULL THEN CASE WHEN partition_type = 'DAYS' THEN - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); - partition_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); WHEN partition_type = 'MONTHS' THEN - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); ELSE - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); END CASE; - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - LOOP - IF partition != partition_by_max_ttl_date THEN - IF partition_year IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 3)::integer < partition_year::integer THEN - partition_to_delete := partition; - ELSE - IF partition_month IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 4)::integer < partition_month::integer THEN - partition_to_delete := partition; + IF partition_by_max_ttl_year IS NULL THEN + RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; + ELSE + IF partition_type = 'YEARS' THEN + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END LOOP; + ELSE + IF partition_type = 'MONTHS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; ELSE - IF partition_day IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 5)::integer < partition_day::integer THEN - partition_to_delete := partition; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_year = partition_by_max_ttl_year::integer THEN + IF partition_month >= partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; END IF; END IF; END IF; + END LOOP; + END IF; + ELSE + IF partition_type = 'DAYS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + IF partition_by_max_ttl_day IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_month > partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_month < partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_day := SPLIT_PART(partition, '_', 5)::integer; + IF partition_day >= partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_day < partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END LOOP; END IF; END IF; END IF; - IF partition_to_delete IS NOT NULL THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete; - EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete); - partition_to_delete := NULL; - deleted := deleted + 1; - END IF; END IF; - END LOOP; + END IF; + END IF; END IF; END IF; END @@ -107,8 +193,6 @@ BEGIN partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); WHEN partition_type = 'YEARS' THEN partition := 'ts_kv_' || to_char(date, 'yyyy'); - WHEN partition_type = 'INDEFINITE' THEN - partition := NULL; ELSE partition := NULL; END CASE; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java index 7aa8881bed..3cdd1f71bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java @@ -45,9 +45,7 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService { @Scheduled(initialDelayString = "${sql.ttl.edge_events.execution_interval_ms}", fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}") public void cleanUp() { if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { - log.info("Going to cleanup old edge events using ttl: {}s", ttl); - long totalEdgeEventsRemoved = edgeService.cleanupEvents(ttl); - log.info("Total edge events removed by TTL: [{}]", totalEdgeEventsRemoved); + edgeService.cleanupEvents(ttl); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java index b244e1b8fd..a51910b7ed 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EventsCleanUpService.java @@ -48,9 +48,7 @@ public class EventsCleanUpService extends AbstractCleanUpService { @Scheduled(initialDelayString = "${sql.ttl.events.execution_interval_ms}", fixedDelayString = "${sql.ttl.events.execution_interval_ms}") public void cleanUp() { if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { - log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugTtl, ttl); - long totalEventsRemoved = eventService.cleanupEvents(ttl, debugTtl); - log.info("Total events removed by TTL: [{}]", totalEventsRemoved); + eventService.cleanupEvents(ttl, debugTtl); } } diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java index 7094b34e62..55c746b580 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/TimeseriesCleanUpService.java @@ -18,11 +18,15 @@ package org.thingsboard.server.service.ttl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.ttl.AbstractCleanUpService; +@TbCoreComponent @Slf4j +@Service public class TimeseriesCleanUpService extends AbstractCleanUpService { @Value("${sql.ttl.ts.ts_key_value_ttl}") diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index 8567077eb1..53472fd261 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -94,5 +94,5 @@ public interface EdgeService { String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId); - long cleanupEvents(long ttl); + void cleanupEvents(long ttl); } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java index dce49afdb1..db1c77697e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/event/EventService.java @@ -46,6 +46,6 @@ public interface EventService { void removeEvents(TenantId tenantId, EntityId entityId); - long cleanupEvents(long ttl, long debugTtl); + void cleanupEvents(long ttl, long debugTtl); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java index eda1075cd6..60ef98a801 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeDao.java @@ -180,7 +180,6 @@ public interface EdgeDao extends Dao { /** * Executes stored procedure to cleanup old edge events. * @param ttl the ttl for edge events in seconds - * @return the number of deleted edge events */ - long cleanupEvents(long ttl); + void cleanupEvents(long ttl); } \ No newline at end of file diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index d06ce8d722..0089853969 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -628,8 +628,8 @@ public class EdgeServiceImpl extends AbstractEntityService implements EdgeServic } @Override - public long cleanupEvents(long ttl) { - return edgeDao.cleanupEvents(ttl); + public void cleanupEvents(long ttl) { + edgeDao.cleanupEvents(ttl); } private List findEdgeRuleChains(TenantId tenantId, EdgeId edgeId) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java index 4cfd0a3ffa..2785df90df 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/BaseEventService.java @@ -132,8 +132,8 @@ public class BaseEventService implements EventService { } @Override - public long cleanupEvents(long ttl, long debugTtl) { - return eventDao.cleanupEvents(ttl, debugTtl); + public void cleanupEvents(long ttl, long debugTtl) { + eventDao.cleanupEvents(ttl, debugTtl); } private DataValidator eventValidator = diff --git a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java index d943937ca3..ceacbffd50 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/event/EventDao.java @@ -106,7 +106,6 @@ public interface EventDao extends Dao { * Executes stored procedure to cleanup old events. Uses separate ttl for debug and other events. * @param otherEventsTtl the ttl for events in seconds * @param debugEventsTtl the ttl for debug events in seconds - * @return the number of deleted events */ - long cleanupEvents(long otherEventsTtl, long debugEventsTtl); + void cleanupEvents(long otherEventsTtl, long debugEventsTtl); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java index 8e7112e81d..ec6d730230 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java @@ -40,9 +40,9 @@ import org.thingsboard.server.dao.model.sql.EdgeInfoEntity; import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -198,17 +198,19 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao imple } @Override - public long cleanupEvents(long ttl) { + public void cleanupEvents(long ttl) { try { - CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_edge_events_by_ttl(?,?,?)}"); + log.info("Going to cleanup old edge events using ttl: {}s", ttl); + PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_edge_events_by_ttl(?,?)"); stmt.setLong(1, ttl); - stmt.registerOutParameter(3, Types.BIGINT); - stmt.executeUpdate(); + stmt.setLong(2, 0); + stmt.execute(); printWarnings(stmt); - return stmt.getLong(3); + ResultSet resultSet = stmt.getResultSet(); + resultSet.next(); + log.info("Total edge events removed by TTL: [{}]", resultSet.getLong(1)); } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); - return 0; + log.error("SQLException occurred during edge events TTL task execution ", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index edaf95a29d..cde7d75d32 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -39,12 +39,9 @@ import org.thingsboard.server.dao.event.EventDao; import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; -import javax.sql.DataSource; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.SQLWarning; -import java.sql.Statement; -import java.sql.Types; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -262,18 +259,20 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen } @Override - public long cleanupEvents(long otherEventsTtl, long debugEventsTtl) { + public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { try { - CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_events_by_ttl(?,?,?)}"); + log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugEventsTtl, otherEventsTtl); + PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_events_by_ttl(?,?,?)"); stmt.setLong(1, otherEventsTtl); stmt.setLong(2, debugEventsTtl); - stmt.registerOutParameter(3, Types.BIGINT); - stmt.executeUpdate(); + stmt.setLong(3, 0); + stmt.execute(); printWarnings(stmt); - return stmt.getLong(3); + ResultSet resultSet = stmt.getResultSet(); + resultSet.next(); + log.info("Total events removed by TTL: [{}]", resultSet.getLong(1)); } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); - return 0; + log.error("SQLException occurred during events TTL task execution ", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index abd8f15e77..f1636740c3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -29,9 +29,9 @@ import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import javax.annotation.Nullable; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; @@ -69,15 +69,17 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries public void cleanup(long systemTtl) { try { log.info("Going to cleanup old timeseries data using ttl: {}s", systemTtl); - CallableStatement stmt = dataSource.getConnection().prepareCall("{call cleanup_timeseries_by_ttl(?,?,?)}"); + PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_timeseries_by_ttl(?,?,?)"); stmt.setObject(1, ModelConstants.NULL_UUID); stmt.setLong(2, systemTtl); - stmt.registerOutParameter(3, Types.BIGINT); - stmt.executeUpdate(); + stmt.setLong(3, 0); + stmt.execute(); printWarnings(stmt); - log.info("Total telemetry removed stats by TTL for entities: [{}]", stmt.getLong(3)); + ResultSet resultSet = stmt.getResultSet(); + resultSet.next(); + log.info("Total telemetry removed stats by TTL for entities: [{}]", resultSet.getLong(1)); } catch (SQLException e) { - log.error("SQLException occurred during TTL task execution ", e); + log.error("SQLException occurred during timeseries TTL task execution ", e); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index 594f77ae78..9b18dcde7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -27,7 +27,6 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.TsKvEntry; -import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sqlts.AbstractChunkedAggregationTimeseriesDao; import org.thingsboard.server.dao.sqlts.insert.psql.PsqlPartitioningRepository; @@ -36,9 +35,9 @@ import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; -import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Types; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; @@ -107,13 +106,15 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa private void cleanupPartitions(long systemTtl) { try { log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl); - CallableStatement stmt = dataSource.getConnection().prepareCall("{call drop_partitions_by_max_ttl(?,?,?)}"); - stmt.setObject(1, partitioning); + PreparedStatement stmt = dataSource.getConnection().prepareStatement("call drop_partitions_by_max_ttl(?,?,?)"); + stmt.setString(1, partitioning); stmt.setLong(2, systemTtl); - stmt.registerOutParameter(3, Types.BIGINT); - stmt.executeUpdate(); + stmt.setLong(3, 0); + stmt.execute(); printWarnings(stmt); - log.info("Total partitions removed by TTL: [{}]", stmt.getLong(3)); + ResultSet resultSet = stmt.getResultSet(); + resultSet.next(); + log.info("Total partitions removed by TTL: [{}]", resultSet.getLong(1)); } catch (SQLException e) { log.error("SQLException occurred during TTL task execution ", e); } diff --git a/dao/src/main/resources/sql/schema-ts-psql.sql b/dao/src/main/resources/sql/schema-ts-psql.sql index 5683cc0a17..2744ff5a07 100644 --- a/dao/src/main/resources/sql/schema-ts-psql.sql +++ b/dao/src/main/resources/sql/schema-ts-psql.sql @@ -38,17 +38,18 @@ CREATE OR REPLACE PROCEDURE drop_partitions_by_max_ttl(IN partition_type varchar LANGUAGE plpgsql AS $$ DECLARE - max_tenant_ttl bigint; - max_customer_ttl bigint; - max_ttl bigint; - date timestamp; - partition_by_max_ttl_date varchar; - partition_month varchar; - partition_day varchar; - partition_year varchar; - partition varchar; - partition_to_delete varchar; - + max_tenant_ttl bigint; + max_customer_ttl bigint; + max_ttl bigint; + date timestamp; + partition_by_max_ttl_date varchar; + partition_by_max_ttl_month varchar; + partition_by_max_ttl_day varchar; + partition_by_max_ttl_year varchar; + partition varchar; + partition_year integer; + partition_month integer; + partition_day integer; BEGIN SELECT max(attribute_kv.long_v) @@ -65,53 +66,138 @@ BEGIN if max_ttl IS NOT NULL AND max_ttl > 0 THEN date := to_timestamp(EXTRACT(EPOCH FROM current_timestamp) - max_ttl); partition_by_max_ttl_date := get_partition_by_max_ttl_date(partition_type, date); + RAISE NOTICE 'Date by max ttl: %', date; RAISE NOTICE 'Partition by max ttl: %', partition_by_max_ttl_date; IF partition_by_max_ttl_date IS NOT NULL THEN CASE WHEN partition_type = 'DAYS' THEN - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); - partition_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_day := SPLIT_PART(partition_by_max_ttl_date, '_', 5); WHEN partition_type = 'MONTHS' THEN - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); - partition_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_month := SPLIT_PART(partition_by_max_ttl_date, '_', 4); ELSE - partition_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); + partition_by_max_ttl_year := SPLIT_PART(partition_by_max_ttl_date, '_', 3); END CASE; - FOR partition IN SELECT tablename - FROM pg_tables - WHERE schemaname = 'public' - AND tablename like 'ts_kv_' || '%' - AND tablename != 'ts_kv_latest' - AND tablename != 'ts_kv_dictionary' - AND tablename != 'ts_kv_indefinite' - LOOP - IF partition != partition_by_max_ttl_date THEN - IF partition_year IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 3)::integer < partition_year::integer THEN - partition_to_delete := partition; - ELSE - IF partition_month IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 4)::integer < partition_month::integer THEN - partition_to_delete := partition; + IF partition_by_max_ttl_year IS NULL THEN + RAISE NOTICE 'Failed to remove partitions by max ttl date due to partition_by_max_ttl_year is null!'; + ELSE + IF partition_type = 'YEARS' THEN + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END LOOP; + ELSE + IF partition_type = 'MONTHS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove months partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; ELSE - IF partition_day IS NOT NULL THEN - IF SPLIT_PART(partition, '_', 5)::integer < partition_day::integer THEN - partition_to_delete := partition; + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_year = partition_by_max_ttl_year::integer THEN + IF partition_month >= partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; END IF; END IF; END IF; + END LOOP; + END IF; + ELSE + IF partition_type = 'DAYS' THEN + IF partition_by_max_ttl_month IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_month is null!'; + ELSE + IF partition_by_max_ttl_day IS NULL THEN + RAISE NOTICE 'Failed to remove days partitions by max ttl date due to partition_by_max_ttl_day is null!'; + ELSE + FOR partition IN SELECT tablename + FROM pg_tables + WHERE schemaname = 'public' + AND tablename like 'ts_kv_' || '%' + AND tablename != 'ts_kv_latest' + AND tablename != 'ts_kv_dictionary' + AND tablename != 'ts_kv_indefinite' + AND tablename != partition_by_max_ttl_date + LOOP + partition_year := SPLIT_PART(partition, '_', 3)::integer; + IF partition_year > partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_year < partition_by_max_ttl_year::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_month := SPLIT_PART(partition, '_', 4)::integer; + IF partition_month > partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_month < partition_by_max_ttl_month::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + ELSE + partition_day := SPLIT_PART(partition, '_', 5)::integer; + IF partition_day >= partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Skip iteration! Partition: % is valid!', partition; + CONTINUE; + ELSE + IF partition_day < partition_by_max_ttl_day::integer THEN + RAISE NOTICE 'Partition to delete by max ttl: %', partition; + EXECUTE format('DROP TABLE IF EXISTS %I', partition); + deleted := deleted + 1; + END IF; + END IF; + END IF; + END IF; + END IF; + END IF; + END LOOP; END IF; END IF; END IF; - IF partition_to_delete IS NOT NULL THEN - RAISE NOTICE 'Partition to delete by max ttl: %', partition_to_delete; - EXECUTE format('DROP TABLE IF EXISTS %I', partition_to_delete); - partition_to_delete := NULL; - deleted := deleted + 1; - END IF; END IF; - END LOOP; + END IF; + END IF; END IF; END IF; END @@ -127,8 +213,6 @@ BEGIN partition := 'ts_kv_' || to_char(date, 'yyyy') || '_' || to_char(date, 'MM'); WHEN partition_type = 'YEARS' THEN partition := 'ts_kv_' || to_char(date, 'yyyy'); - WHEN partition_type = 'INDEFINITE' THEN - partition := NULL; ELSE partition := NULL; END CASE; From 9c633eeebe04f4293943c0d7d9198a09960846ec Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 7 Jun 2021 15:59:30 +0300 Subject: [PATCH 023/231] added try with resource to auto-close the connections, statements and result sets --- .../server/dao/sql/edge/JpaEdgeDao.java | 14 ++++++++------ .../server/dao/sql/event/JpaBaseEventDao.java | 14 ++++++++------ .../server/dao/sqlts/AbstractSqlTimeseriesDao.java | 14 ++++++++------ .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 14 ++++++++------ 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java index ec6d730230..2249de109f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaEdgeDao.java @@ -40,6 +40,7 @@ import org.thingsboard.server.dao.model.sql.EdgeInfoEntity; import org.thingsboard.server.dao.relation.RelationDao; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -199,16 +200,17 @@ public class JpaEdgeDao extends JpaAbstractSearchTextDao imple @Override public void cleanupEvents(long ttl) { - try { - log.info("Going to cleanup old edge events using ttl: {}s", ttl); - PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_edge_events_by_ttl(?,?)"); + log.info("Going to cleanup old edge events using ttl: {}s", ttl); + try (Connection connection = dataSource.getConnection(); + PreparedStatement stmt = connection.prepareStatement("call cleanup_edge_events_by_ttl(?,?)")) { stmt.setLong(1, ttl); stmt.setLong(2, 0); stmt.execute(); printWarnings(stmt); - ResultSet resultSet = stmt.getResultSet(); - resultSet.next(); - log.info("Total edge events removed by TTL: [{}]", resultSet.getLong(1)); + try (ResultSet resultSet = stmt.getResultSet()) { + resultSet.next(); + log.info("Total edge events removed by TTL: [{}]", resultSet.getLong(1)); + } } catch (SQLException e) { log.error("SQLException occurred during edge events TTL task execution ", e); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java index cde7d75d32..ee09bb7a90 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/event/JpaBaseEventDao.java @@ -39,6 +39,7 @@ import org.thingsboard.server.dao.event.EventDao; import org.thingsboard.server.dao.model.sql.EventEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -260,17 +261,18 @@ public class JpaBaseEventDao extends JpaAbstractDao implemen @Override public void cleanupEvents(long otherEventsTtl, long debugEventsTtl) { - try { - log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugEventsTtl, otherEventsTtl); - PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_events_by_ttl(?,?,?)"); + log.info("Going to cleanup old events using debug events ttl: {}s and other events ttl: {}s", debugEventsTtl, otherEventsTtl); + try (Connection connection = dataSource.getConnection(); + PreparedStatement stmt = connection.prepareStatement("call cleanup_events_by_ttl(?,?,?)")) { stmt.setLong(1, otherEventsTtl); stmt.setLong(2, debugEventsTtl); stmt.setLong(3, 0); stmt.execute(); printWarnings(stmt); - ResultSet resultSet = stmt.getResultSet(); - resultSet.next(); - log.info("Total events removed by TTL: [{}]", resultSet.getLong(1)); + try (ResultSet resultSet = stmt.getResultSet()){ + resultSet.next(); + log.info("Total events removed by TTL: [{}]", resultSet.getLong(1)); + } } catch (SQLException e) { log.error("SQLException occurred during events TTL task execution ", e); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index f1636740c3..eb12984753 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -29,6 +29,7 @@ import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import javax.annotation.Nullable; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -67,17 +68,18 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries private long systemTtl; public void cleanup(long systemTtl) { - try { - log.info("Going to cleanup old timeseries data using ttl: {}s", systemTtl); - PreparedStatement stmt = dataSource.getConnection().prepareStatement("call cleanup_timeseries_by_ttl(?,?,?)"); + log.info("Going to cleanup old timeseries data using ttl: {}s", systemTtl); + try (Connection connection = dataSource.getConnection(); + PreparedStatement stmt = connection.prepareStatement("call cleanup_timeseries_by_ttl(?,?,?)")) { stmt.setObject(1, ModelConstants.NULL_UUID); stmt.setLong(2, systemTtl); stmt.setLong(3, 0); stmt.execute(); printWarnings(stmt); - ResultSet resultSet = stmt.getResultSet(); - resultSet.next(); - log.info("Total telemetry removed stats by TTL for entities: [{}]", resultSet.getLong(1)); + try (ResultSet resultSet = stmt.getResultSet()) { + resultSet.next(); + log.info("Total telemetry removed stats by TTL for entities: [{}]", resultSet.getLong(1)); + } } catch (SQLException e) { log.error("SQLException occurred during timeseries TTL task execution ", e); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index 9b18dcde7e..c23e615548 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -35,6 +35,7 @@ import org.thingsboard.server.dao.timeseries.SqlTsPartitionDate; import org.thingsboard.server.dao.util.PsqlDao; import org.thingsboard.server.dao.util.SqlTsDao; +import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -104,17 +105,18 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa } private void cleanupPartitions(long systemTtl) { - try { - log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl); - PreparedStatement stmt = dataSource.getConnection().prepareStatement("call drop_partitions_by_max_ttl(?,?,?)"); + log.info("Going to cleanup old timeseries data partitions using partition type: {} and ttl: {}s", partitioning, systemTtl); + try (Connection connection = dataSource.getConnection(); + PreparedStatement stmt = connection.prepareStatement("call drop_partitions_by_max_ttl(?,?,?)")) { stmt.setString(1, partitioning); stmt.setLong(2, systemTtl); stmt.setLong(3, 0); stmt.execute(); printWarnings(stmt); - ResultSet resultSet = stmt.getResultSet(); - resultSet.next(); - log.info("Total partitions removed by TTL: [{}]", resultSet.getLong(1)); + try (ResultSet resultSet = stmt.getResultSet()) { + resultSet.next(); + log.info("Total partitions removed by TTL: [{}]", resultSet.getLong(1)); + } } catch (SQLException e) { log.error("SQLException occurred during TTL task execution ", e); } From 523b118ef219f129e881b181c1d7242b644ddf76 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 9 Jun 2021 13:29:42 +0300 Subject: [PATCH 024/231] Structure for the Lwm2mDeviceProfileTransportConfiguration --- .../Lwm2mDeviceTransportConfiguration.java | 4 +- .../data/lwm2m/BootstrapConfiguration.java | 30 ++++++++++ .../device/data/lwm2m/ObjectAttributes.java | 31 ++++++++++ .../device/data/lwm2m/OtherConfiguration.java | 27 +++++++++ .../lwm2m/TelemetryMappingConfiguration.java | 32 ++++++++++ ...2mDeviceProfileTransportConfiguration.java | 25 +++----- ...java => DefaultLwM2MUplinkMsgHandler.java} | 59 +++++++------------ .../server/DefaultLwM2mTransportService.java | 4 +- .../lwm2m/server/LwM2mServerListener.java | 4 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 4 +- .../server/LwM2mTransportCoapResource.java | 6 +- .../lwm2m/server/LwM2mTransportRequest.java | 2 +- .../lwm2m/server/LwM2mTransportUtil.java | 51 ++++++++-------- ...andler.java => LwM2mUplinkMsgHandler.java} | 2 +- .../lwm2m/server/client/LwM2mClient.java | 4 +- .../server/client/LwM2mClientRpcRequest.java | 12 ++-- .../lwm2m/server/client/LwM2mFwSwUpdate.java | 22 +++---- 17 files changed, 207 insertions(+), 112 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/TelemetryMappingConfiguration.java rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{DefaultLwM2MTransportMsgHandler.java => DefaultLwM2MUplinkMsgHandler.java} (97%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{LwM2mTransportMsgHandler.java => LwM2mUplinkMsgHandler.java} (98%) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/Lwm2mDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/Lwm2mDeviceTransportConfiguration.java index 14901ef69f..510bfeda7f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/Lwm2mDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/Lwm2mDeviceTransportConfiguration.java @@ -19,8 +19,10 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration; import java.util.HashMap; import java.util.Map; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.java new file mode 100644 index 0000000000..66bdfd1f4c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/BootstrapConfiguration.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.device.data.lwm2m; + +import lombok.Data; + +import java.util.Map; + +@Data +public class BootstrapConfiguration { + + //TODO: define the objects; + private Map servers; + private Map lwm2mServer; + private Map bootstrapServer; + +} 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 new file mode 100644 index 0000000000..4e01f645bc --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/ObjectAttributes.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.lwm2m; + +import lombok.Data; + +@Data +public class ObjectAttributes { + + private Integer dim; + private String ver; + private Integer pmin; + private Integer pmax; + private Integer gt; + private Integer lt; + private Integer st; + +} 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 new file mode 100644 index 0000000000..2bfef74391 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/OtherConfiguration.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.device.data.lwm2m; + +import lombok.Data; + +@Data +public class OtherConfiguration { + + private Integer fwUpdateStrategy; + private Integer swUpdateStrategy; + private Integer clientOnlyObserveAfterConnect; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/TelemetryMappingConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/TelemetryMappingConfiguration.java new file mode 100644 index 0000000000..2f2683998d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/lwm2m/TelemetryMappingConfiguration.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.device.data.lwm2m; + +import lombok.Data; + +import java.util.Map; +import java.util.Set; + +@Data +public class TelemetryMappingConfiguration { + + private Map keyName; + private Set observe; + private Set attribute; + private Set telemetry; + private Map attributeLwm2m; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java index 34e281fd73..31ae4594ea 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/Lwm2mDeviceProfileTransportConfiguration.java @@ -15,31 +15,20 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.fasterxml.jackson.annotation.JsonAnyGetter; -import com.fasterxml.jackson.annotation.JsonAnySetter; -import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; - -import java.util.HashMap; -import java.util.Map; +import org.thingsboard.server.common.data.device.data.lwm2m.BootstrapConfiguration; +import org.thingsboard.server.common.data.device.data.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.data.lwm2m.TelemetryMappingConfiguration; @Data public class Lwm2mDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - @JsonIgnore - private Map properties = new HashMap<>(); - - @JsonAnyGetter - public Map properties() { - return this.properties; - } + private static final long serialVersionUID = 6257277825459600068L; - @JsonAnySetter - public void put(String name, Object value) { - this.properties.put(name, value); - } + private TelemetryMappingConfiguration observeAttr; + private BootstrapConfiguration bootstrap; + private OtherConfiguration clientLwM2mSettings; @Override public DeviceTransportType getType() { 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/DefaultLwM2MUplinkMsgHandler.java similarity index 97% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MTransportMsgHandler.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java index 4f97273cb6..c94f44f55c 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/DefaultLwM2MUplinkMsgHandler.java @@ -89,10 +89,8 @@ 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.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.UPDATING; 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.DEVICE_ATTRIBUTES_REQUEST; @@ -125,7 +123,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.v @Slf4j @Service @TbLwM2mTransportComponent -public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler { +public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private ExecutorService registrationExecutor; private ExecutorService updateRegistrationExecutor; @@ -144,11 +142,11 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler private final Map rpcSubscriptions; public final Map firmwareUpdateState; - public DefaultLwM2MTransportMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, - LwM2mClientContext clientContext, - @Lazy LwM2mTransportRequest lwM2mTransportRequest, - OtaPackageDataCache otaPackageDataCache, - LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) { + public DefaultLwM2MUplinkMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, + LwM2mClientContext clientContext, + @Lazy LwM2mTransportRequest lwM2mTransportRequest, + OtaPackageDataCache otaPackageDataCache, + LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) { this.transportService = transportService; this.config = config; this.helper = helper; @@ -233,6 +231,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler updateRegistrationExecutor.submit(() -> { LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); try { + log.warn("[{}] [{{}] Client: update after Registration", registration.getEndpoint(), registration.getId()); clientContext.updateRegistration(lwM2MClient, registration); TransportProtos.SessionInfoProto sessionInfo = lwM2MClient.getSession(); this.reportActivityAndRegister(sessionInfo); @@ -267,8 +266,8 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler clientContext.unregister(client, registration); SessionInfoProto sessionInfo = client.getSession(); if (sessionInfo != null) { + transportService.process(sessionInfo, DefaultTransportService.getSessionEventMsg(SessionEvent.CLOSED), null); transportService.deregisterSession(sessionInfo); - this.doCloseSession(sessionInfo); sessionStore.remove(registration.getEndpoint()); log.info("Client close session: [{}] unReg [{}] name [{}] profile ", registration.getId(), registration.getEndpoint(), sessionInfo.getDeviceType()); } else { @@ -552,19 +551,6 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler transportService.deregisterSession(sessionInfo); } - /** - * Session device in thingsboard is closed - * - * @param sessionInfo - lwm2m client - */ - private void doCloseSession(SessionInfoProto sessionInfo) { - TransportProtos.SessionEvent event = SessionEvent.CLOSED; - TransportProtos.SessionEventMsg msg = TransportProtos.SessionEventMsg.newBuilder() - .setSessionType(TransportProtos.SessionType.ASYNC) - .setEvent(event).build(); - transportService.process(sessionInfo, msg, null); - } - /** * Those methods are called by the protocol stage thread pool, this means that execution MUST be done in a short delay, * * if you need to do long time processing use a dedicated thread pool. @@ -609,20 +595,20 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler * @param lwM2MClient - object with All parameters off client */ private void initClientTelemetry(LwM2mClient lwM2MClient) { - LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(lwM2MClient.getProfileId()); - Set clientObjects = clientContext.getSupportedIdVerInClient(lwM2MClient); - if (clientObjects != null && clientObjects.size() > 0) { - if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == lwM2MClientProfile.getClientStrategy()) { + LwM2mClientProfile profile = clientContext.getProfile(lwM2MClient.getProfileId()); + Set supportedObjects = clientContext.getSupportedIdVerInClient(lwM2MClient); + if (supportedObjects != null && supportedObjects.size() > 0) { + if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == profile.getClientStrategy()) { // #2 - lwM2MClient.getPendingReadRequests().addAll(clientObjects); - clientObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, READ, + lwM2MClient.getPendingReadRequests().addAll(supportedObjects); + supportedObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, READ, null, this.config.getTimeout(), null)); } // #1 - this.initReadAttrTelemetryObserveToClient(lwM2MClient, READ, clientObjects); - this.initReadAttrTelemetryObserveToClient(lwM2MClient, OBSERVE, clientObjects); - this.initReadAttrTelemetryObserveToClient(lwM2MClient, WRITE_ATTRIBUTES, clientObjects); - this.initReadAttrTelemetryObserveToClient(lwM2MClient, DISCOVER, clientObjects); + this.initReadAttrTelemetryObserveToClient(lwM2MClient, profile, READ, supportedObjects); + this.initReadAttrTelemetryObserveToClient(lwM2MClient, profile, OBSERVE, supportedObjects); + this.initReadAttrTelemetryObserveToClient(lwM2MClient, profile, WRITE_ATTRIBUTES, supportedObjects); + this.initReadAttrTelemetryObserveToClient(lwM2MClient, profile, DISCOVER, supportedObjects); } } @@ -717,8 +703,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler } } - private void initReadAttrTelemetryObserveToClient(LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, Set clientObjects) { - LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(lwM2MClient.getProfileId()); + private void initReadAttrTelemetryObserveToClient(LwM2mClient lwM2MClient, LwM2mClientProfile lwM2MClientProfile, LwM2mTypeOper typeOper, Set supportedObjects) { Set result = null; ConcurrentHashMap params = null; if (READ.equals(typeOper)) { @@ -738,7 +723,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()); result = params.keySet(); } - sendRequestsToClient(lwM2MClient, typeOper, clientObjects, result, params); + sendRequestsToClient(lwM2MClient, typeOper, supportedObjects, result, params); } private void sendRequestsToClient(LwM2mClient lwM2MClient, LwM2mTypeOper operationType, Set supportedObjectIds, Set desiredObjectIds, ConcurrentHashMap params) { @@ -1325,7 +1310,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler if (lwM2MClient.getRegistration().getSupportedVersion(FW_5_ID) != null) { SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); if (sessionInfo != null) { - DefaultLwM2MTransportMsgHandler handler = this; + DefaultLwM2MUplinkMsgHandler handler = this; this.transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.FIRMWARE.name()), new TransportServiceCallback<>() { @Override @@ -1375,7 +1360,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler if (lwM2MClient.getRegistration().getSupportedVersion(SW_ID) != null) { SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); if (sessionInfo != null) { - DefaultLwM2MTransportMsgHandler handler = this; + DefaultLwM2MUplinkMsgHandler handler = this; transportService.process(sessionInfo, createOtaPackageRequestMsg(sessionInfo, OtaPackageType.SOFTWARE.name()), new TransportServiceCallback<>() { @Override 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 9be164023d..c6711c31d3 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 @@ -26,7 +26,6 @@ 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.EditableSecurityStore; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; @@ -35,7 +34,6 @@ 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.server.store.TbEditableSecurityStore; import org.thingsboard.server.transport.lwm2m.server.store.TbSecurityStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -81,7 +79,7 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; private final LwM2mTransportServerHelper helper; - private final DefaultLwM2MTransportMsgHandler handler; + private final DefaultLwM2MUplinkMsgHandler handler; private final CaliforniumRegistrationStore registrationStore; private final TbSecurityStore securityStore; private final LwM2mClientContext lwM2mClientContext; 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 b69825843e..594148022c 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.LwM2mTransportUtil.c @Slf4j public class LwM2mServerListener { - private final LwM2mTransportMsgHandler service; + private final LwM2mUplinkMsgHandler service; - public LwM2mServerListener(LwM2mTransportMsgHandler service) { + public LwM2mServerListener(LwM2mUplinkMsgHandler 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 b71de7db1b..39a2194966 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 @@ -36,10 +36,10 @@ import java.util.UUID; @Slf4j public class LwM2mSessionMsgListener implements GenericFutureListener>, SessionMsgListener { - private DefaultLwM2MTransportMsgHandler handler; + private DefaultLwM2MUplinkMsgHandler handler; private TransportProtos.SessionInfoProto sessionInfo; - public LwM2mSessionMsgListener(DefaultLwM2MTransportMsgHandler handler, TransportProtos.SessionInfoProto sessionInfo) { + public LwM2mSessionMsgListener(DefaultLwM2MUplinkMsgHandler handler, TransportProtos.SessionInfoProto sessionInfo) { this.handler = handler; this.sessionInfo = sessionInfo; } 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 4e45b172cb..c19859a36d 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 @@ -37,9 +37,9 @@ 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 LwM2mTransportMsgHandler handler; + private final LwM2mUplinkMsgHandler handler; - public LwM2mTransportCoapResource(LwM2mTransportMsgHandler handler, String name) { + public LwM2mTransportCoapResource(LwM2mUplinkMsgHandler handler, String name) { super(name); this.handler = handler; this.setObservable(true); // enable observing @@ -150,7 +150,7 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { } private byte[] getOtaData(UUID currentId) { - return ((DefaultLwM2MTransportMsgHandler) handler).otaPackageDataCache.get(currentId.toString()); + return ((DefaultLwM2MUplinkMsgHandler) handler).otaPackageDataCache.get(currentId.toString()); } } 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 3568000ead..fc9290cab0 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 @@ -107,7 +107,7 @@ public class LwM2mTransportRequest { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; private final LwM2mClientContext lwM2mClientContext; - private final DefaultLwM2MTransportMsgHandler handler; + private final DefaultLwM2MUplinkMsgHandler handler; @PostConstruct public void init() { 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 1d1feab85e..6159a201b4 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 @@ -770,33 +770,34 @@ public class LwM2mTransportUtil { * "/3_1.0/0": {"gt": 17}, * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}} */ + //TODO: refactor this to valid jackson code instead of parsing manually. public static LwM2mClientProfile toLwM2MClientProfile(DeviceProfile deviceProfile) { - if (((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { - Object profile = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); - try { - ObjectMapper mapper = new ObjectMapper(); - String profileStr = mapper.writeValueAsString(profile); - JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; - return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportUtil.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; - } catch (IOException e) { - log.error("", e); - } - } +// if (((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { +// Object profile = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); +// try { +// ObjectMapper mapper = new ObjectMapper(); +// String profileStr = mapper.writeValueAsString(profile); +// JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; +// return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportUtil.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; +// } catch (IOException e) { +// log.error("", e); +// } +// } return null; } public static JsonObject getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) { - if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { - Object bootstrap = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); - try { - ObjectMapper mapper = new ObjectMapper(); - String bootstrapStr = mapper.writeValueAsString(bootstrap); - JsonObject objectMsg = (bootstrapStr != null) ? validateJson(bootstrapStr) : null; - return (getValidateBootstrapProfileFromThingsboard(objectMsg)) ? objectMsg.get(BOOTSTRAP).getAsJsonObject() : null; - } catch (IOException e) { - log.error("", e); - } - } +// if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { +// Object bootstrap = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); +// try { +// ObjectMapper mapper = new ObjectMapper(); +// String bootstrapStr = mapper.writeValueAsString(bootstrap); +// JsonObject objectMsg = (bootstrapStr != null) ? validateJson(bootstrapStr) : null; +// return (getValidateBootstrapProfileFromThingsboard(objectMsg)) ? objectMsg.get(BOOTSTRAP).getAsJsonObject() : null; +// } catch (IOException e) { +// log.error("", e); +// } +// } return null; } @@ -1001,12 +1002,12 @@ public class LwM2mTransportUtil { * Attribute pmax = new Attribute(MAXIMUM_PERIOD, "60"); * Attribute [] attrs = {gt, st}; */ - public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, DefaultLwM2MTransportMsgHandler serviceImpl) { + public static SimpleDownlinkRequest createWriteAttributeRequest(String target, Object params, DefaultLwM2MUplinkMsgHandler serviceImpl) { AttributeSet attrSet = new AttributeSet(createWriteAttributes(params, serviceImpl, target)); return attrSet.getAttributes().size() > 0 ? new WriteAttributesRequest(target, attrSet) : null; } - private static Attribute[] createWriteAttributes(Object params, DefaultLwM2MTransportMsgHandler serviceImpl, String target) { + private static Attribute[] createWriteAttributes(Object params, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) { List attributeLists = new ArrayList<>(); ObjectMapper oMapper = new ObjectMapper(); Map map = oMapper.convertValue(params, ConcurrentHashMap.class); @@ -1059,7 +1060,7 @@ public class LwM2mTransportUtil { } } - public static Object convertWriteAttributes(String type, Object value, DefaultLwM2MTransportMsgHandler serviceImpl, String target) { + public static Object convertWriteAttributes(String type, Object value, DefaultLwM2MUplinkMsgHandler serviceImpl, String target) { switch (type) { /** Integer [0:255]; */ case DIMENSION: 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/LwM2mUplinkMsgHandler.java similarity index 98% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportMsgHandler.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mUplinkMsgHandler.java index ee3b07da34..fcccf29f1d 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/LwM2mUplinkMsgHandler.java @@ -28,7 +28,7 @@ import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientRpcReques import java.util.Collection; import java.util.Optional; -public interface LwM2mTransportMsgHandler { +public interface LwM2mUplinkMsgHandler { void onRegistered(Registration registration, Collection previousObsersations); 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 78f9ffb349..519f32b076 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 @@ -36,7 +36,7 @@ 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.DefaultLwM2MTransportMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -373,7 +373,7 @@ public class LwM2mClient implements Cloneable { .collect(Collectors.toSet()); } - public void initReadValue(DefaultLwM2MTransportMsgHandler serviceImpl, String path) { + public void initReadValue(DefaultLwM2MUplinkMsgHandler serviceImpl, String path) { if (path != null) { this.pendingReadRequests.remove(path); } 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 33525426e3..53385b6f61 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 @@ -24,7 +24,7 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MTransportMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MUplinkMsgHandler; import java.util.Map; import java.util.Objects; @@ -79,7 +79,7 @@ public class LwM2mClientRpcRequest { } public LwM2mClientRpcRequest(LwM2mTypeOper lwM2mTypeOper, String bodyParams, int requestId, - TransportProtos.SessionInfoProto sessionInfo, Registration registration, DefaultLwM2MTransportMsgHandler handler) { + TransportProtos.SessionInfoProto sessionInfo, Registration registration, DefaultLwM2MUplinkMsgHandler handler) { this.registration = registration; this.sessionInfo = sessionInfo; this.requestId = requestId; @@ -110,7 +110,7 @@ public class LwM2mClientRpcRequest { .build(); } - private void init(DefaultLwM2MTransportMsgHandler handler) { + private void init(DefaultLwM2MUplinkMsgHandler handler) { try { // #1 if (this.bodyParams.contains(KEY_NAME_KEY)) { @@ -179,7 +179,7 @@ public class LwM2mClientRpcRequest { } } - private void setValidParamsKey(DefaultLwM2MTransportMsgHandler handler) { + private void setValidParamsKey(DefaultLwM2MUplinkMsgHandler handler) { String paramsStr = this.getValueKeyFromBody(PARAMS_KEY); if (paramsStr != null) { String params2Json = @@ -245,7 +245,7 @@ public class LwM2mClientRpcRequest { } private ConcurrentHashMap convertParamsToResourceId(ConcurrentHashMap params, - DefaultLwM2MTransportMsgHandler serviceImpl) { + DefaultLwM2MUplinkMsgHandler serviceImpl) { Map paramsIdVer = new ConcurrentHashMap<>(); LwM2mPath targetId = new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(this.targetIdVer))); if (targetId.isObjectInstance()) { @@ -272,7 +272,7 @@ public class LwM2mClientRpcRequest { return (ConcurrentHashMap) paramsIdVer; } - private String getRezIdByResourceNameAndObjectInstanceId(String resourceName, DefaultLwM2MTransportMsgHandler handler) { + private String getRezIdByResourceNameAndObjectInstanceId(String resourceName, DefaultLwM2MUplinkMsgHandler handler) { LwM2mClient lwM2mClient = handler.clientContext.getClientBySessionInfo(this.sessionInfo); return lwM2mClient != null ? lwM2mClient.getRezIdByResourceNameAndObjectInstanceId(resourceName, this.targetIdVer, handler.config.getModelProvider()) : 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 6b289bac59..a13253282e 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 @@ -24,7 +24,7 @@ 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.DefaultLwM2MTransportMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportRequest; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -156,7 +156,7 @@ public class LwM2mFwSwUpdate { } } - public void initReadValue(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request, String pathIdVer) { + public void initReadValue(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request, String pathIdVer) { if (pathIdVer != null) { this.pendingInfoRequestsStart.remove(pathIdVer); } @@ -176,7 +176,7 @@ public class LwM2mFwSwUpdate { * Send FsSw to Lwm2mClient: * before operation Write: fw_state = DOWNLOADING */ - public void writeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { + public void writeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request) { if (this.currentId != null) { this.stateUpdate = OtaPackageUpdateStatus.INITIATED.name(); this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); @@ -214,7 +214,7 @@ public class LwM2mFwSwUpdate { } } - public void sendLogs(DefaultLwM2MTransportMsgHandler handler, String typeOper, String typeInfo, String 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); @@ -230,7 +230,7 @@ public class LwM2mFwSwUpdate { * fw_state/sw_state = UPDATING * send execute */ - public void executeFwSwWare(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { + public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request) { this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); request.sendAllRequest(this.lwM2MClient, this.pathInstallId, EXECUTE, null, 0, this.rpcRequest); } @@ -248,7 +248,7 @@ public class LwM2mFwSwUpdate { * -- 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(DefaultLwM2MTransportMsgHandler handler) { + 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); @@ -313,7 +313,7 @@ public class LwM2mFwSwUpdate { * - 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(DefaultLwM2MTransportMsgHandler handler) { + private boolean conditionalSwUpdateStart(DefaultLwM2MUplinkMsgHandler handler) { Long updateResultSw = (Long) this.lwM2MClient.getResourceValue(null, this.pathResultId); // #1/#2 return updateResultSw >= LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code || @@ -347,7 +347,7 @@ public class LwM2mFwSwUpdate { * --- send to telemetry ( key - this is name Update Result in model) ( * -- fw_state/sw_state = FAILED */ - public void finishFwSwUpdate(DefaultLwM2MTransportMsgHandler handler, boolean success) { + 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 : LwM2mTransportUtil.UpdateResultSw.fromUpdateResultSwByCode(updateResult.intValue()).type; @@ -381,7 +381,7 @@ public class LwM2mFwSwUpdate { return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult; } - private void observeStateUpdate(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request) { + private void observeStateUpdate(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request) { request.sendAllRequest(lwM2MClient, convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE, null, null, 0, null); @@ -390,7 +390,7 @@ public class LwM2mFwSwUpdate { null, null, 0, null); } - public void sendSateOnThingsBoard(DefaultLwM2MTransportMsgHandler handler) { + 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)); @@ -433,7 +433,7 @@ public class LwM2mFwSwUpdate { * - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully") * - finished operation Execute (FwUpdate) */ - public void updateStateOta(DefaultLwM2MTransportMsgHandler handler, LwM2mTransportRequest request, + public void updateStateOta(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request, Registration registration, String path, int value) { if (OBJ_5_BINARY.code == this.getUpdateStrategy()) { if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { From dd748245706903847ba72f7c6066e97e026ebe28 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 9 Jun 2021 13:30:28 +0300 Subject: [PATCH 025/231] js-executor: Refactoring Kafka executor for use async/await --- msa/js-executor/queue/kafkaTemplate.js | 68 +++++++++++--------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index 7fce429d23..b34727c977 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -36,11 +36,10 @@ let producer; const configEntries = []; let batchMessages = []; -let batchResolvers = []; let sendLoopInstance; function KafkaProducer() { - this.send = (responseTopic, scriptId, rawResponse, headers) => { + this.send = async (responseTopic, scriptId, rawResponse, headers) => { logger.debug('Pending queue response, scriptId: [%s]', scriptId); const message = { topic: responseTopic, @@ -51,60 +50,50 @@ function KafkaProducer() { }] }; - return pushMessageToSendLater(message); + await pushMessageToSendLater(message); } } -function pushMessageToSendLater(message) { - let resolver; - const promise = new Promise((resolve, reject) => { - resolver = resolve; - }); +async function pushMessageToSendLater(message) { batchMessages.push(message); - batchResolvers.push(resolver); if (batchMessages.length >= maxBatchSize) { - sendMessagesAsBatch(); - sendLoopWithLinger(); //reset loop function and reschedule new linger + await sendMessagesAsBatch(true); } - return promise; } function sendLoopWithLinger() { if (sendLoopInstance) { - logger.debug("Clear sendLoop scheduler. Starting new send loop with linger [%s]", linger); - clearInterval(sendLoopInstance); + clearTimeout(sendLoopInstance); } else { logger.debug("Starting new send loop with linger [%s]", linger) } - sendLoopInstance = setInterval(sendMessagesAsBatch, linger); + sendLoopInstance = setTimeout(sendMessagesAsBatch, linger); } -function sendMessagesAsBatch() { +async function sendMessagesAsBatch(isImmediately) { + if (sendLoopInstance) { + logger.debug("sendMessagesAsBatch: Clear sendLoop scheduler. Starting new send loop with linger [%s]", linger); + clearTimeout(sendLoopInstance); + } + sendLoopInstance = null; if (batchMessages.length > 0) { - logger.debug('sendMessagesAsBatch, length: [%s]', batchMessages.length); + logger.debug('sendMessagesAsBatch, length: [%s], %s', batchMessages.length, isImmediately ? 'immediately' : ''); const messagesToSend = batchMessages; - const resolvers = batchResolvers; batchMessages = []; - batchResolvers = []; - producer.sendBatch({ - topicMessages: messagesToSend, - acks: acks, - compression: compressionType - }).then( - () => { - logger.debug('Response batch sent to kafka, length: [%s]', messagesToSend.length); - for (let i = 0; i < resolvers.length; i++) { - resolvers[i](); - } - }, - (err) => { - logger.error('Failed batch send to kafka, length: [%s], pending to reprocess msgs', messagesToSend.length); - logger.error(err.stack); - batchMessages = messagesToSend.concat(batchMessages); - batchResolvers = resolvers.concat(batchResolvers); //promises will never be rejected. Will retry forever - } - ); + try { + await producer.sendBatch({ + topicMessages: messagesToSend, + acks: acks, + compression: compressionType + }) + logger.debug('Response batch sent to kafka, length: [%s]', messagesToSend.length); + } catch(err) { + logger.error('Failed batch send to kafka, length: [%s], pending to reprocess msgs', messagesToSend.length); + logger.error(err.stack); + batchMessages = messagesToSend.concat(batchMessages); + } } + sendLoopWithLinger(); } (async () => { @@ -285,9 +274,8 @@ async function disconnectProducer() { producer = null; try { logger.info('Stopping loop...'); - //TODO: send handle msg - clearInterval(sendLoopInstance); - sendMessagesAsBatch(); + clearTimeout(sendLoopInstance); + await sendMessagesAsBatch(); await _producer.disconnect(); logger.info('Kafka Producer stopped.'); } catch (e) { From a7a7dca3b3eb76166cf8ed93bf764f2c8c5935b2 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 8 Jun 2021 20:03:45 +0300 Subject: [PATCH 026/231] js-executor: upgraded libraries to fix vulnerability warning (@google-cloud/pubsub", amqplib) --- msa/js-executor/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index 75f009880b..280979afa6 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -13,8 +13,8 @@ }, "dependencies": { "@azure/service-bus": "^1.1.9", - "@google-cloud/pubsub": "^2.5.0", - "amqplib": "^0.6.0", + "@google-cloud/pubsub": "^2.12.0", + "amqplib": "^0.8.0", "aws-sdk": "^2.741.0", "azure-sb": "^0.11.1", "config": "^3.3.1", From 98bea8eaf579f6641bc8a3dfa43639901aca7c91 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 8 Jun 2021 20:14:10 +0300 Subject: [PATCH 027/231] js-executor: http livenessProbe added --- msa/js-executor/api/httpServer.js | 46 +++++++++++++++++++ .../config/custom-environment-variables.yml | 1 + msa/js-executor/config/default.yml | 1 + msa/js-executor/package.json | 1 + msa/js-executor/server.js | 2 + 5 files changed, 51 insertions(+) create mode 100644 msa/js-executor/api/httpServer.js diff --git a/msa/js-executor/api/httpServer.js b/msa/js-executor/api/httpServer.js new file mode 100644 index 0000000000..996638c7c3 --- /dev/null +++ b/msa/js-executor/api/httpServer.js @@ -0,0 +1,46 @@ +/* + * 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. + */ + +const config = require('config'), + logger = require('../config/logger')._logger('httpServer'), + express = require('express'); + +const httpPort = Number(config.get('http_port')); + +const app = express(); + +app.get('/livenessProbe', async (req, res) => { + const date = new Date(); + const message = { now: date.toISOString() }; + res.send(message); +}) + +app.listen(httpPort, () => logger.info(`Started http endpoint on port ${httpPort}. Please, use /livenessProbe !`)) \ No newline at end of file diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index c5eb15e8b9..7773fdaf0c 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -16,6 +16,7 @@ queue_type: "TB_QUEUE_TYPE" #kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) request_topic: "REMOTE_JS_EVAL_REQUEST_TOPIC" +http_port: "HTTP_PORT" # /livenessProbe js: response_poll_interval: "REMOTE_JS_RESPONSE_POLL_INTERVAL_MS" diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 5a2c47a675..bcefc1c639 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -16,6 +16,7 @@ queue_type: "kafka" request_topic: "js_eval.requests" +http_port: "8888" # /livenessProbe js: response_poll_interval: "25" diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index 280979afa6..ba940ee85a 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -18,6 +18,7 @@ "aws-sdk": "^2.741.0", "azure-sb": "^0.11.1", "config": "^3.3.1", + "express": "^4.17.1", "js-yaml": "^3.14.0", "kafkajs": "^1.15.0", "long": "^4.0.0", diff --git a/msa/js-executor/server.js b/msa/js-executor/server.js index 0c415cc156..7b2e7e59b8 100644 --- a/msa/js-executor/server.js +++ b/msa/js-executor/server.js @@ -51,3 +51,5 @@ switch (serviceType) { process.exit(-1); } +require('./api/httpServer'); + From d109941cf919527db9b574356389f5f71ddef2c5 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 9 Jun 2021 18:33:20 +0300 Subject: [PATCH 028/231] Redis Security Store implementation, tests for RPK and PSK security and code cleaning --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 94 ++++++++++ .../lwm2m/NoSecLwM2MIntegrationTest.java | 81 +------- .../lwm2m/PskRpkLwm2mIntegrationTest.java | 57 ++++++ .../lwm2m/X509LwM2MIntegrationTest.java | 137 ++------------ .../data/device/credentials/lwm2m/HasKey.java | 21 ++- .../secure/LwM2MBootstrapConfig.java | 3 +- ...LwM2mCredentialsSecurityInfoValidator.java | 8 +- .../lwm2m/secure/TbLwM2MSecurityInfo.java | 4 +- .../lwm2m/secure/TbX509DtlsSessionInfo.java | 4 +- .../store/TbLwM2MDtlsSessionRedisStore.java | 17 +- .../store/TbLwM2mRedisSecurityStore.java | 176 +++++++++--------- .../transport/auth/TransportDeviceInfo.java | 4 +- .../ValidateDeviceCredentialsResponse.java | 4 +- 13 files changed, 291 insertions(+), 319 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/PskRpkLwm2mIntegrationTest.java 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..8f7d4fb295 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 @@ -17,24 +17,43 @@ package org.thingsboard.server.transport.lwm2m; import com.fasterxml.jackson.core.type.TypeReference; import org.apache.commons.io.IOUtils; +import org.eclipse.californium.core.network.config.NetworkConfig; +import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; +import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Assert; import org.junit.Before; 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.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.credentials.lwm2m.LwM2MClientCredentials; 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.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.controller.AbstractWebsocketTest; import org.thingsboard.server.controller.TbTestWebSocketClient; import org.thingsboard.server.dao.service.DaoSqlTest; +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 java.io.IOException; import java.io.InputStream; @@ -54,9 +73,13 @@ import java.security.spec.ECPrivateKeySpec; import java.security.spec.ECPublicKeySpec; import java.security.spec.KeySpec; import java.util.Base64; +import java.util.Collections; +import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + @DaoSqlTest public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { @@ -137,6 +160,16 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { // certificates trustedby the server (should contain rootCA) protected final Certificate[] trustedCertificates = new Certificate[1]; + protected final int SECURE_PORT = 5686; + protected final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); + protected final String SECURE_ENDPOINT = "deviceAEndpoint"; + protected final String SECURE_URI = "coaps://localhost:" + SECURE_PORT; + + private final int PORT = 5686; + private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(PORT)); + private final String ENDPOINT = "deviceAEndpoint"; + private final String SERVER_URI = "coaps://localhost:" + PORT; + public AbstractLwM2MIntegrationTest() { // create client credentials try { @@ -260,10 +293,71 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { Assert.assertNotNull(deviceProfile); } + @NotNull + private Device createDevice(LwM2MClientCredentials clientCredentials) 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); + + LwM2MCredentials credentials = new LwM2MCredentials(); + + credentials.setClient(clientCredentials); + + deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); + doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); + return device; + } + @After public void after() { executor.shutdownNow(); wsClient.close(); } + public void basicTestConnectionObserveTelemetry(Security security, + LwM2MClientCredentials credentials, + NetworkConfig coapConfig, + String endpoint) throws Exception { + createDeviceProfile(TRANSPORT_CONFIGURATION); + Device device = createDevice(credentials); + + 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, endpoint); + + client.init(security, coapConfig); + 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/NoSecLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/NoSecLwM2MIntegrationTest.java index 7d173e61d9..e7a36b75af 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,32 +17,10 @@ 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.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Device; 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; -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 java.util.Collections; -import java.util.List; import static org.eclipse.leshan.client.object.Security.noSec; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @@ -51,64 +29,11 @@ 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"); - 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); - - LwM2MCredentials noSecCredentials = new LwM2MCredentials(); - NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); - clientCredentials.setEndpoint(ENDPOINT); - noSecCredentials.setClient(clientCredentials); - deviceCredentials.setCredentialsValue(JacksonUtil.toString(noSecCredentials)); - doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); - return device; - } - @Test public void testConnectAndObserveTelemetry() throws Exception { - createDeviceProfile(TRANSPORT_CONFIGURATION); - - Device device = createDevice(); - - 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, ENDPOINT); - client.init(SECURITY, COAP_CONFIG); - 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(); + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); + clientCredentials.setEndpoint(ENDPOINT); + super.basicTestConnectionObserveTelemetry(SECURITY, clientCredentials, COAP_CONFIG, ENDPOINT); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/PskRpkLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/PskRpkLwm2mIntegrationTest.java new file mode 100644 index 0000000000..0d2f4d05a5 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/PskRpkLwm2mIntegrationTest.java @@ -0,0 +1,57 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.eclipse.leshan.client.object.Security; +import org.eclipse.leshan.core.util.Hex; +import org.junit.Test; +import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; + +import java.nio.charset.StandardCharsets; + +import static org.eclipse.leshan.client.object.Security.psk; +import static org.eclipse.leshan.client.object.Security.rpk; + +public class PskRpkLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest { + @Test + public void testConnectWithRPKAndObserveTelemetry() throws Exception { + RPKClientCredentials rpkClientCredentials = new RPKClientCredentials(); + rpkClientCredentials.setEndpoint(SECURE_ENDPOINT); + rpkClientCredentials.setKey(Hex.encodeHexString(clientPublicKey.getEncoded())); + Security security = rpk(SECURE_URI, + 123, + clientPublicKey.getEncoded(), + clientPrivateKey.getEncoded(), + serverX509Cert.getPublicKey().getEncoded()); + super.basicTestConnectionObserveTelemetry(security, rpkClientCredentials, SECURE_COAP_CONFIG, SECURE_ENDPOINT); + } + + @Test + public void testConnectWithPSKAndObserveTelemetry() throws Exception { + String pskIdentity = "SOME_PSK_ID"; + String pskKey = "73656372657450534b"; + PSKClientCredentials clientCredentials = new PSKClientCredentials(); + clientCredentials.setEndpoint(SECURE_ENDPOINT); + clientCredentials.setKey(pskKey); + clientCredentials.setIdentity(pskIdentity); + Security security = psk(SECURE_URI, + 123, + pskIdentity.getBytes(StandardCharsets.UTF_8), + Hex.decodeHex(pskKey.toCharArray())); + super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, SECURE_ENDPOINT); + } +} 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..8e3c26298d 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 @@ -15,145 +15,36 @@ */ 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.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.device.credentials.lwm2m.X509ClientCredentials; -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.common.transport.util.SslUtil; -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 java.util.Collections; -import java.util.List; import static org.eclipse.leshan.client.object.Security.x509; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { - - private final int port = 5686; - private final NetworkConfig coapConfig = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(port)); - 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"); - 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); - - LwM2MCredentials credentials = new LwM2MCredentials(); - - credentials.setClient(clientCredentials); - - deviceCredentials.setCredentialsValue(JacksonUtil.toString(credentials)); - doPost("/api/device/credentials", deviceCredentials).andExpect(status().isOk()); - return device; - } - @Test public void testConnectAndObserveTelemetry() throws Exception { - createDeviceProfile(TRANSPORT_CONFIGURATION); X509ClientCredentials credentials = new X509ClientCredentials(); - credentials.setEndpoint(endpoint); - Device device = createDevice(credentials); - - 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, endpoint); - Security security = x509(serverUri, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); - client.init(security, coapConfig); - 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(); + credentials.setEndpoint(SECURE_ENDPOINT); + Security security = x509(SECURE_URI, + 123, + clientX509Cert.getEncoded(), + clientPrivateKeyFromCert.getEncoded(), + serverX509Cert.getEncoded()); + super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, SECURE_ENDPOINT); } @Test public void testConnectWithCertAndObserveTelemetry() throws Exception { - createDeviceProfile(TRANSPORT_CONFIGURATION); X509ClientCredentials credentials = new X509ClientCredentials(); - credentials.setEndpoint(endpoint); + credentials.setEndpoint(SECURE_ENDPOINT); credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); - Device device = createDevice(credentials); - - 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, endpoint); - - Security security = x509(serverUri, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); - - client.init(security, coapConfig); - 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(); + Security security = x509(SECURE_URI, + 123, + clientX509CertNotTrusted.getEncoded(), + clientPrivateKeyFromCert.getEncoded(), + serverX509Cert.getEncoded()); + super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, SECURE_ENDPOINT); } - } 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/HasKey.java index ec62765298..58cfa84099 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/HasKey.java @@ -15,20 +15,25 @@ */ package org.thingsboard.server.common.data.device.credentials.lwm2m; +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Getter; +import lombok.Setter; import lombok.SneakyThrows; import org.apache.commons.codec.binary.Hex; public abstract class HasKey extends AbstractLwM2MClientCredentials { - private byte[] key; + @Getter + @Setter + private String key; + + private byte[] keyInBytes; @SneakyThrows - public void setKey(String key) { - if (key != null) { - this.key = Hex.decodeHex(key.toLowerCase().toCharArray()); + @JsonIgnore + public byte[] getDecodedKey() { + if (keyInBytes == null) { + keyInBytes = Hex.decodeHex(key.toLowerCase().toCharArray()); } - } - - public byte[] getKey() { - return key; + return keyInBytes; } } 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 8ffc3ac1f2..30ac8e01c3 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 @@ -21,10 +21,11 @@ import org.eclipse.leshan.core.request.BindingMode; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.bootstrap.BootstrapConfig; +import java.io.Serializable; import java.nio.charset.StandardCharsets; @Data -public class LwM2MBootstrapConfig { +public class LwM2MBootstrapConfig implements Serializable { /* interface BootstrapSecurityConfig servers: BootstrapServersSecurityConfig, 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..cbb7dd15f4 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 @@ -135,10 +135,10 @@ public class LwM2mCredentialsSecurityInfoValidator { PSKClientCredentials pskConfig = (PSKClientCredentials) clientCredentialsConfig; if (StringUtils.isNotEmpty(pskConfig.getIdentity())) { try { - if (pskConfig.getKey() != null && pskConfig.getKey().length > 0) { + if (pskConfig.getDecodedKey() != null && pskConfig.getDecodedKey().length > 0) { endpoint = StringUtils.isNotEmpty(pskConfig.getEndpoint()) ? pskConfig.getEndpoint() : endpoint; if (endpoint != null && !endpoint.isEmpty()) { - result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getKey())); + result.setSecurityInfo(SecurityInfo.newPreSharedKeyInfo(endpoint, pskConfig.getIdentity(), pskConfig.getDecodedKey())); result.setSecurityMode(PSK); } } @@ -153,8 +153,8 @@ public class LwM2mCredentialsSecurityInfoValidator { private void createClientSecurityInfoRPK(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredentials clientCredentialsConfig) { RPKClientCredentials rpkConfig = (RPKClientCredentials) clientCredentialsConfig; try { - if (rpkConfig.getKey() != null) { - PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getKey()); + if (rpkConfig.getDecodedKey() != null) { + PublicKey key = SecurityUtil.publicKey.decode(rpkConfig.getDecodedKey()); result.setSecurityInfo(SecurityInfo.newRawPublicKeyInfo(endpoint, key)); result.setSecurityMode(RPK); } else { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java index 9b9147c44f..bc45a77b58 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MSecurityInfo.java @@ -23,8 +23,10 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2MBootstrapConfig; +import java.io.Serializable; + @Data -public class TbLwM2MSecurityInfo { +public class TbLwM2MSecurityInfo implements Serializable { private ValidateDeviceCredentialsResponse msg; private SecurityInfo securityInfo; private SecurityMode securityMode; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java index 1c038a9440..03490fe69d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbX509DtlsSessionInfo.java @@ -18,8 +18,10 @@ package org.thingsboard.server.transport.lwm2m.secure; import lombok.Data; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; +import java.io.Serializable; + @Data -public class TbX509DtlsSessionInfo { +public class TbX509DtlsSessionInfo implements Serializable { private final String x509CommonName; private final ValidateDeviceCredentialsResponse credentials; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java index b1c4b85e2a..616688a620 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MDtlsSessionRedisStore.java @@ -15,28 +15,29 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import com.fasterxml.jackson.databind.JsonNode; +import org.nustaq.serialization.FSTConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.transport.lwm2m.secure.TbX509DtlsSessionInfo; public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { private static final String SESSION_EP = "SESSION#EP#"; - RedisConnectionFactory connectionFactory; + private final RedisConnectionFactory connectionFactory; + private final FSTConfiguration serializer; public TbLwM2MDtlsSessionRedisStore(RedisConnectionFactory redisConnectionFactory) { this.connectionFactory = redisConnectionFactory; + this.serializer = FSTConfiguration.createDefaultConfiguration(); } @Override public void put(String endpoint, TbX509DtlsSessionInfo msg) { try (var c = connectionFactory.getConnection()) { - var msgJson = JacksonUtil.convertValue(msg, JsonNode.class); - if (msgJson != null) { - c.set(getKey(endpoint), msgJson.toString().getBytes()); + var serializedMsg = serializer.asByteArray(msg); + if (serializedMsg != null) { + c.set(getKey(endpoint), serializedMsg); } else { - throw new RuntimeException("Problem with serialization of message: " + msg.toString()); + throw new RuntimeException("Problem with serialization of message: " + msg); } } } @@ -46,7 +47,7 @@ public class TbLwM2MDtlsSessionRedisStore implements TbLwM2MDtlsSessionStore { try (var c = connectionFactory.getConnection()) { var data = c.get(getKey(endpoint)); if (data != null) { - return JacksonUtil.fromString(new String(data), TbX509DtlsSessionInfo.class); + return (TbX509DtlsSessionInfo) serializer.asObject(data); } else { return null; } 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 9e3fe5625d..b108286afd 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 @@ -15,49 +15,55 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import org.eclipse.leshan.server.redis.serialization.SecurityInfoSerDes; -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.data.redis.connection.RedisClusterConnection; +import org.nustaq.serialization.FSTConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; -import org.springframework.data.redis.core.Cursor; -import org.springframework.data.redis.core.ScanOptions; +import org.springframework.integration.redis.util.RedisLockRegistry; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; -import java.util.ArrayList; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; +import java.util.concurrent.locks.Lock; public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { private static final String SEC_EP = "SEC#EP#"; - + private static final String LOCK_EP = "LOCK#EP#"; private static final String PSKID_SEC = "PSKID#SEC"; private final RedisConnectionFactory connectionFactory; - private SecurityStoreListener listener; + private final FSTConfiguration serializer; + private final RedisLockRegistry redisLock; public TbLwM2mRedisSecurityStore(RedisConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; + redisLock = new RedisLockRegistry(connectionFactory, "Security"); + serializer = FSTConfiguration.createDefaultConfiguration(); } @Override public SecurityInfo getByEndpoint(String endpoint) { + Lock lock = null; try (var connection = connectionFactory.getConnection()) { + lock = redisLock.obtain(toLockKey(endpoint)); + lock.lock(); byte[] data = connection.get((SEC_EP + endpoint).getBytes()); if (data == null) { return null; } else { - return deserialize(data); + return ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); + } + } finally { + if (lock != null) { + lock.unlock(); } } } @Override public SecurityInfo getByIdentity(String identity) { + Lock lock = null; try (var connection = connectionFactory.getConnection()) { + lock = redisLock.obtain(toLockKey(identity)); + lock.lock(); byte[] ep = connection.hGet(PSKID_SEC.getBytes(), identity.getBytes()); if (ep == null) { return null; @@ -66,102 +72,86 @@ public class TbLwM2mRedisSecurityStore implements TbEditableSecurityStore { if (data == null) { return null; } else { - return deserialize(data); + return ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); } } + } finally { + if (lock != null) { + lock.unlock(); + } } } @Override public void put(TbLwM2MSecurityInfo tbSecurityInfo) throws NonUniqueSecurityInfoException { - //TODO: implement + SecurityInfo info = tbSecurityInfo.getSecurityInfo(); + byte[] tbSecurityInfoSerialized = serializer.asByteArray(tbSecurityInfo); + Lock lock = null; + try (var connection = connectionFactory.getConnection()) { + lock = redisLock.obtain(tbSecurityInfo.getEndpoint()); + lock.lock(); + if (info != null && info.getIdentity() != null) { + byte[] oldEndpointBytes = connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); + if (oldEndpointBytes != null) { + String oldEndpoint = new String(oldEndpointBytes); + if (!oldEndpoint.equals(info.getEndpoint())) { + throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); + } + connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); + } + } + + byte[] previousData = connection.getSet((SEC_EP + tbSecurityInfo.getEndpoint()).getBytes(), tbSecurityInfoSerialized); + if (previousData != null && info != null) { + String previousIdentity = ((TbLwM2MSecurityInfo) serializer.asObject(previousData)).getSecurityInfo().getIdentity(); + if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { + connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); + } + } + } finally { + if (lock != null) { + lock.unlock(); + } + } } @Override public TbLwM2MSecurityInfo getTbLwM2MSecurityInfoByEndpoint(String endpoint) { - //TODO: implement - return null; + Lock lock = null; + try (var connection = connectionFactory.getConnection()) { + lock = redisLock.obtain(endpoint); + lock.lock(); + byte[] data = connection.get((SEC_EP + endpoint).getBytes()); + return (TbLwM2MSecurityInfo) serializer.asObject(data); + } finally { + if (lock != null) { + lock.unlock(); + } + } } @Override public void remove(String endpoint) { - //TODO: implement - } - - // @Override -// public Collection getAll() { -// try (var connection = connectionFactory.getConnection()) { -// Collection list = new LinkedList<>(); -// ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(SEC_EP + "*").build(); -// List> scans = new ArrayList<>(); -// if (connection instanceof RedisClusterConnection) { -// ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> { -// scans.add(((RedisClusterConnection) connection).scan(node, scanOptions)); -// }); -// } else { -// scans.add(connection.scan(scanOptions)); -// } -// -// scans.forEach(scan -> { -// scan.forEachRemaining(key -> { -// byte[] element = connection.get(key); -// list.add(deserialize(element)); -// }); -// }); -// return list; -// } -// } -// -// @Override -// public SecurityInfo add(SecurityInfo info) throws NonUniqueSecurityInfoException { -// byte[] data = serialize(info); -// try (var connection = connectionFactory.getConnection()) { -// if (info.getIdentity() != null) { -// // populate the secondary index (security info by PSK id) -// String oldEndpoint = new String(connection.hGet(PSKID_SEC.getBytes(), info.getIdentity().getBytes())); -// if (!oldEndpoint.equals(info.getEndpoint())) { -// throw new NonUniqueSecurityInfoException("PSK Identity " + info.getIdentity() + " is already used"); -// } -// connection.hSet(PSKID_SEC.getBytes(), info.getIdentity().getBytes(), info.getEndpoint().getBytes()); -// } -// -// byte[] previousData = connection.getSet((SEC_EP + info.getEndpoint()).getBytes(), data); -// SecurityInfo previous = previousData == null ? null : deserialize(previousData); -// String previousIdentity = previous == null ? null : previous.getIdentity(); -// if (previousIdentity != null && !previousIdentity.equals(info.getIdentity())) { -// connection.hDel(PSKID_SEC.getBytes(), previousIdentity.getBytes()); -// } -// -// return previous; -// } -// } -// -// @Override -// public SecurityInfo remove(String endpoint, boolean infosAreCompromised) { -// try (var connection = connectionFactory.getConnection()) { -// byte[] data = connection.get((SEC_EP + endpoint).getBytes()); -// -// if (data != null) { -// SecurityInfo info = deserialize(data); -// if (info.getIdentity() != null) { -// connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); -// } -// connection.del((SEC_EP + endpoint).getBytes()); -// if (listener != null) { -// listener.securityInfoRemoved(infosAreCompromised, info); -// } -// return info; -// } -// } -// return null; -// } - - private byte[] serialize(SecurityInfo secInfo) { - return SecurityInfoSerDes.serialize(secInfo); + Lock lock = null; + try (var connection = connectionFactory.getConnection()) { + lock = redisLock.obtain(endpoint); + lock.lock(); + byte[] data = connection.get((SEC_EP + endpoint).getBytes()); + if (data != null) { + SecurityInfo info = ((TbLwM2MSecurityInfo) serializer.asObject(data)).getSecurityInfo(); + if (info != null && info.getIdentity() != null) { + connection.hDel(PSKID_SEC.getBytes(), info.getIdentity().getBytes()); + } + connection.del((SEC_EP + endpoint).getBytes()); + } + } finally { + if (lock != null) { + lock.unlock(); + } + } } - private SecurityInfo deserialize(byte[] data) { - return SecurityInfoSerDes.deserialize(data); + private String toLockKey(String endpoint) { + return LOCK_EP + endpoint; } - } 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 27d42d867f..433a5af8ee 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 @@ -21,8 +21,10 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; +import java.io.Serializable; + @Data -public class TransportDeviceInfo { +public class TransportDeviceInfo implements Serializable { private TenantId tenantId; private CustomerId customerId; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceCredentialsResponse.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceCredentialsResponse.java index e1324791b3..d54dce18fb 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceCredentialsResponse.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/auth/ValidateDeviceCredentialsResponse.java @@ -19,9 +19,11 @@ import lombok.Builder; import lombok.Data; import org.thingsboard.server.common.data.DeviceProfile; +import java.io.Serializable; + @Data @Builder -public class ValidateDeviceCredentialsResponse implements DeviceProfileAware { +public class ValidateDeviceCredentialsResponse implements DeviceProfileAware, Serializable { private final TransportDeviceInfo deviceInfo; private final DeviceProfile deviceProfile; From 38d3b9c2936140159b93d2033abbe64ec6cb911a Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Wed, 9 Jun 2021 18:47:45 +0300 Subject: [PATCH 029/231] Constants improvement --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 17 +++++++++-------- .../lwm2m/NoSecLwM2MIntegrationTest.java | 9 --------- 2 files changed, 9 insertions(+), 17 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 8f7d4fb295..39b4d5ef2f 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 @@ -78,6 +78,7 @@ import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import static org.eclipse.leshan.client.object.Security.noSec; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @DaoSqlTest @@ -160,15 +161,15 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { // certificates trustedby the server (should contain rootCA) protected final Certificate[] trustedCertificates = new Certificate[1]; - protected final int SECURE_PORT = 5686; - protected final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); - protected final String SECURE_ENDPOINT = "deviceAEndpoint"; - protected final String SECURE_URI = "coaps://localhost:" + SECURE_PORT; + protected static final int SECURE_PORT = 5686; + protected static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); + protected static final String SECURE_ENDPOINT = "deviceAEndpoint"; + protected static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT; - private final int PORT = 5686; - private final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(PORT)); - private final String ENDPOINT = "deviceAEndpoint"; - private final String SERVER_URI = "coaps://localhost:" + PORT; + protected static final int PORT = 5685; + protected static final Security SECURITY = noSec("coap://localhost:" + PORT, 123); + protected static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); + protected static final String ENDPOINT = "deviceAEndpoint"; public AbstractLwM2MIntegrationTest() { // create client credentials 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 e7a36b75af..233898e81c 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 @@ -15,20 +15,11 @@ */ package org.thingsboard.server.transport.lwm2m; -import org.eclipse.californium.core.network.config.NetworkConfig; -import org.eclipse.leshan.client.object.Security; import org.junit.Test; import org.thingsboard.server.common.data.device.credentials.lwm2m.NoSecClientCredentials; -import static org.eclipse.leshan.client.object.Security.noSec; - 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"; - @Test public void testConnectAndObserveTelemetry() throws Exception { NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); From 3012e5e545ba266e7f8326c1aa8749a06a5f85c3 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 9 Jun 2021 19:02:03 +0300 Subject: [PATCH 030/231] Refactoring of the Device profile update --- .../device/data/lwm2m/ObjectAttributes.java | 14 +- .../secure/LwM2MBootstrapSecurityStore.java | 51 ++- .../server/DefaultLwM2MUplinkMsgHandler.java | 317 ++++++++---------- .../lwm2m/server/LwM2mTransportRequest.java | 130 +++++-- .../lwm2m/server/LwM2mTransportUtil.java | 167 +++------ .../server/LwM2mVersionedModelProvider.java | 2 +- .../lwm2m/server/client/LwM2mClient.java | 49 +-- .../server/client/LwM2mClientContext.java | 14 +- .../server/client/LwM2mClientContextImpl.java | 40 +-- .../server/client/LwM2mClientProfile.java | 113 ------- .../server/client/LwM2mClientRpcRequest.java | 10 +- ...ters.java => ParametersAnalyzeResult.java} | 4 +- 12 files changed, 369 insertions(+), 542 deletions(-) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/{ResultsAnalyzerParameters.java => ParametersAnalyzeResult.java} (92%) 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 4e01f645bc..ee54a23a07 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 @@ -15,17 +15,19 @@ */ package org.thingsboard.server.common.data.device.data.lwm2m; +import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; @Data +@JsonInclude(JsonInclude.Include.NON_NULL) public class ObjectAttributes { - private Integer dim; + private Long dim; private String ver; - private Integer pmin; - private Integer pmax; - private Integer gt; - private Integer lt; - private Integer st; + private Long pmin; + private Long pmax; + private Double gt; + private Double lt; + private Double st; } 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 197c3ba842..e9b0edccf3 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,6 +29,8 @@ import org.eclipse.leshan.server.security.BootstrapSecurityStore; import org.eclipse.leshan.server.security.SecurityInfo; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; 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; @@ -151,35 +153,30 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { } private LwM2MBootstrapConfig getParametersBootstrap(TbLwM2MSecurityInfo store) { - try { - LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); - if (lwM2MBootstrapConfig != null) { - ObjectMapper mapper = new ObjectMapper(); - 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); - LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); - UUID sessionUUiD = UUID.randomUUID(); - 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()); - helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_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); - return null; - } + LwM2MBootstrapConfig lwM2MBootstrapConfig = store.getBootstrapCredentialConfig(); + if (lwM2MBootstrapConfig != null) { + BootstrapConfiguration bootstrapObject = getBootstrapParametersFromThingsboard(store.getDeviceProfile()); + lwM2MBootstrapConfig.servers = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getServers()), LwM2MBootstrapServers.class); + LwM2MServerBootstrap profileServerBootstrap = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getBootstrapServer()), LwM2MServerBootstrap.class); + LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class); + UUID sessionUUiD = UUID.randomUUID(); + 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()); + helper.sendParametersOnThingsboardTelemetry(helper.getKvStringtoThingsboard(LOG_LW2M_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); + return null; } - } catch (JsonProcessingException e) { - 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()); return null; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java index c94f44f55c..7653a2c099 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java @@ -15,10 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.server; -import com.fasterxml.jackson.core.type.TypeReference; 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; import com.google.gson.reflect.TypeToken; @@ -36,15 +34,16 @@ import org.eclipse.leshan.core.response.ReadResponse; 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.ota.OtaPackageDataCache; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.data.ota.OtaPackageKey; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageUtil; -import org.thingsboard.server.common.data.id.OtaPackageId; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.AdaptorException; @@ -61,12 +60,11 @@ 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.LwM2mClientProfile; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientRpcRequest; 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.client.ResultsAnalyzerParameters; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -82,16 +80,14 @@ import java.util.Random; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutorService; 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.common.data.ota.OtaPackageUpdateStatus.FAILED; import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.INITIATED; -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.DEVICE_ATTRIBUTES_REQUEST; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_5_ID; @@ -102,18 +98,15 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_WARN; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; 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_CANCEL_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.SW_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertJsonArrayToSet; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertOtaUpdateValueToString; -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.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getAckCallback; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.isFwSwWords; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.setValidTypeOper; @@ -467,7 +460,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { try { LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); Registration registration = client.getRegistration(); - if(registration != null) { + if (registration != null) { lwm2mClientRpcRequest = new LwM2mClientRpcRequest(lwM2mTypeOper, bodyParams, toDeviceRpcRequestMsg.getRequestId(), sessionInfo, registration, this); if (lwm2mClientRpcRequest.getErrorMsg() != null) { lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); @@ -595,20 +588,72 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param lwM2MClient - object with All parameters off client */ private void initClientTelemetry(LwM2mClient lwM2MClient) { - LwM2mClientProfile profile = clientContext.getProfile(lwM2MClient.getProfileId()); + 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.getClientStrategy()) { + if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == profile.getClientLwM2mSettings().getClientOnlyObserveAfterConnect()) { // #2 lwM2MClient.getPendingReadRequests().addAll(supportedObjects); supportedObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, READ, null, this.config.getTimeout(), null)); } // #1 - this.initReadAttrTelemetryObserveToClient(lwM2MClient, profile, READ, supportedObjects); - this.initReadAttrTelemetryObserveToClient(lwM2MClient, profile, OBSERVE, supportedObjects); - this.initReadAttrTelemetryObserveToClient(lwM2MClient, profile, WRITE_ATTRIBUTES, supportedObjects); - this.initReadAttrTelemetryObserveToClient(lwM2MClient, profile, DISCOVER, supportedObjects); + this.sendReadRequests(lwM2MClient, profile, supportedObjects); + this.sendObserveRequests(lwM2MClient, profile, supportedObjects); + this.sendWriteAttributeRequests(lwM2MClient, profile, supportedObjects); +// Removed. Used only for debug. +// this.sendDiscoverRequests(lwM2MClient, profile, supportedObjects); + } + } + + private void sendReadRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { + 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(targetId -> lwM2mTransportRequest.sendReadRequest(lwM2MClient, targetId, this.config.getTimeout())); + } + + 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 -> lwM2mTransportRequest.sendObserveRequest(lwM2MClient, targetId, this.config.getTimeout())); + } + + private void sendWriteAttributeRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { + Map attributesMap = profile.getObserveAttr().getAttributeLwm2m(); + attributesMap = attributesMap.entrySet().stream().filter(target -> isSupportedTargetId(supportedObjects, target.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); +// TODO: why do we need to put observe into pending read requests? +// lwM2MClient.getPendingReadRequests().addAll(targetIds); + attributesMap.forEach((targetId, params) -> lwM2mTransportRequest.sendWriteAttributesRequest(lwM2MClient, targetId, params, this.config.getTimeout())); + } + + + private void sendDiscoverRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { + Set targetIds = profile.getObserveAttr().getAttributeLwm2m().keySet(); + 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 -> lwM2mTransportRequest.sendDiscoverRequest(lwM2MClient, targetId, this.config.getTimeout())); + } + + private void sendRequestsToClient(LwM2mClient lwM2MClient, LwM2mTypeOper operationType, Set supportedObjectIds, Set desiredObjectIds, ConcurrentHashMap params) { + if (desiredObjectIds != null && !desiredObjectIds.isEmpty()) { + Set targetObjectIds = desiredObjectIds.stream().filter(target -> isSupportedTargetId(supportedObjectIds, target) + ).collect(Collectors.toUnmodifiableSet()); + if (!targetObjectIds.isEmpty()) { + //TODO: remove this side effect? + lwM2MClient.getPendingReadRequests().addAll(targetObjectIds); + targetObjectIds.forEach(target -> { + Object additionalParams = params != null ? params.get(target) : null; + lwM2mTransportRequest.sendAllRequest(lwM2MClient, target, operationType, additionalParams, this.config.getTimeout(), null); + }); + if (OBSERVE.equals(operationType)) { + lwM2MClient.initReadValue(this, null); + } + } } } @@ -618,7 +663,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param pathIdVer - */ private void updateObjectResourceValue(Registration registration, LwM2mObject lwM2mObject, String pathIdVer) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObject.getInstances().forEach((instanceId, instance) -> { String pathInstance = pathIds.toString() + "/" + instanceId; this.updateObjectInstanceResourceValue(registration, instance, pathInstance); @@ -631,7 +676,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param pathIdVer - */ private void updateObjectInstanceResourceValue(Registration registration, LwM2mObjectInstance lwM2mObjectInstance, String pathIdVer) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); lwM2mObjectInstance.getResources().forEach((resourceId, resource) -> { String pathRez = pathIds.toString() + "/" + resourceId; this.updateResourcesValue(registration, resource, pathRez); @@ -668,9 +713,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { log.warn("93) path: [{}] value: [{}]", path, lwM2mResource.getValue()); fwUpdate.updateStateOta(this, lwM2mTransportRequest, registration, path, ((Long) lwM2mResource.getValue()).intValue()); } - Set paths = new HashSet<>(); - paths.add(path); - this.updateAttrTelemetry(registration, paths); + this.updateAttrTelemetry(registration, Collections.singleton(path)); } else { log.error("Fail update Resource [{}]", lwM2mResource); } @@ -703,47 +746,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } } - private void initReadAttrTelemetryObserveToClient(LwM2mClient lwM2MClient, LwM2mClientProfile lwM2MClientProfile, LwM2mTypeOper typeOper, Set supportedObjects) { - Set result = null; - ConcurrentHashMap params = null; - if (READ.equals(typeOper)) { - result = JacksonUtil.fromString(lwM2MClientProfile.getPostAttributeProfile().toString(), - new TypeReference<>() { - }); - result.addAll(JacksonUtil.fromString(lwM2MClientProfile.getPostTelemetryProfile().toString(), - new TypeReference<>() { - })); - } else if (OBSERVE.equals(typeOper)) { - result = JacksonUtil.fromString(lwM2MClientProfile.getPostObserveProfile().toString(), - new TypeReference<>() { - }); - } else if (DISCOVER.equals(typeOper)) { - result = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()).keySet(); - } else if (WRITE_ATTRIBUTES.equals(typeOper)) { - params = this.getPathForWriteAttributes(lwM2MClientProfile.getPostAttributeLwm2mProfile()); - result = params.keySet(); - } - sendRequestsToClient(lwM2MClient, typeOper, supportedObjects, result, params); - } - - private void sendRequestsToClient(LwM2mClient lwM2MClient, LwM2mTypeOper operationType, Set supportedObjectIds, Set desiredObjectIds, ConcurrentHashMap params) { - if (desiredObjectIds != null && !desiredObjectIds.isEmpty()) { - Set targetObjectIds = desiredObjectIds.stream().filter(target -> isSupportedTargetId(supportedObjectIds, target) - ).collect(Collectors.toUnmodifiableSet()); - if (!targetObjectIds.isEmpty()) { - //TODO: remove this side effect? - lwM2MClient.getPendingReadRequests().addAll(targetObjectIds); - targetObjectIds.forEach(target -> { - Object additionalParams = params != null ? params.get(target) : null; - lwM2mTransportRequest.sendAllRequest(lwM2MClient, target, operationType, additionalParams, this.config.getTimeout(), null); - }); - if (OBSERVE.equals(operationType)) { - lwM2MClient.initReadValue(this, null); - } - } - } - } - private boolean isSupportedTargetId(Set supportedIds, String targetId) { String[] targetIdParts = targetId.split(LWM2M_SEPARATOR_PATH); if (targetIdParts.length <= 1) { @@ -781,20 +783,20 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private ResultsAddKeyValueProto getParametersFromProfile(Registration registration, Set path) { if (path != null && path.size() > 0) { ResultsAddKeyValueProto results = new ResultsAddKeyValueProto(); - LwM2mClientProfile lwM2MClientProfile = clientContext.getProfile(registration); + var profile = clientContext.getProfile(registration); List resultAttributes = new ArrayList<>(); - lwM2MClientProfile.getPostAttributeProfile().forEach(pathIdVer -> { - if (path.contains(pathIdVer.getAsString())) { - TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer.getAsString(), registration); + profile.getObserveAttr().getAttribute().forEach(pathIdVer -> { + if (path.contains(pathIdVer)) { + TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer, registration); if (kvAttr != null) { resultAttributes.add(kvAttr); } } }); List resultTelemetries = new ArrayList<>(); - lwM2MClientProfile.getPostTelemetryProfile().forEach(pathIdVer -> { - if (path.contains(pathIdVer.getAsString())) { - TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer.getAsString(), registration); + profile.getObserveAttr().getTelemetry().forEach(pathIdVer -> { + if (path.contains(pathIdVer)) { + TransportProtos.KeyValueProto kvAttr = this.getKvToThingsboard(pathIdVer, registration); if (kvAttr != null) { resultTelemetries.add(kvAttr); } @@ -813,12 +815,12 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private TransportProtos.KeyValueProto getKvToThingsboard(String pathIdVer, Registration registration) { LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); - JsonObject names = clientContext.getProfiles().get(lwM2MClient.getProfileId()).getPostKeyNameProfile(); - if (names != null && names.has(pathIdVer)) { - String resourceName = names.get(pathIdVer).getAsString(); + Map names = clientContext.getProfile(lwM2MClient.getProfileId()).getObserveAttr().getKeyName(); + if (names != null && names.containsKey(pathIdVer)) { + String resourceName = names.get(pathIdVer); if (resourceName != null && !resourceName.isEmpty()) { try { - LwM2mResource resourceValue = lwM2MClient != null ? getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer) : null; + LwM2mResource resourceValue = getResourceValueFromLwM2MClient(lwM2MClient, pathIdVer); if (resourceValue != null) { ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); @@ -830,14 +832,14 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { ResourceModel.Type finalCurrentType = currentType; resourceValue.getInstances().forEach((k, v) -> { Object val = this.converter.convertValue(v, finalCurrentType, expectedType, - new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); + new LwM2mPath(fromVersionedIdToObjectId(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))); + new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); } LwM2mOtaConvert lwM2mOtaConvert = convertOtaUpdateValueToString(pathIdVer, valueKvProto, currentType); valueKvProto = lwM2mOtaConvert.getValue(); @@ -864,7 +866,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { ResourceModel.Type currentType = resourceValue.getType(); ResourceModel.Type expectedType = this.helper.getResourceModelTypeEqualsKvProtoValueType(currentType, pathIdVer); return this.converter.convertValue(resourceValue.getValue(), currentType, expectedType, - new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))); + new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))); } else { return null; } @@ -879,7 +881,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { LwM2mResource lwm2mResourceValue = null; ResourceValue resourceValue = lwM2MClient.getResources().get(path); if (resourceValue != null) { - if (new LwM2mPath(convertPathFromIdVerToObjectId(path)).isResource()) { + if (new LwM2mPath(fromVersionedIdToObjectId(path)).isResource()) { lwm2mResourceValue = lwM2MClient.getResources().get(path).getLwM2mResource(); } } @@ -929,53 +931,42 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param clients - * @param deviceProfile - */ + //TODO: review and optimize the logic to minimize number of the requests to device. private void onDeviceProfileUpdate(List clients, DeviceProfile deviceProfile) { - LwM2mClientProfile lwM2MClientProfileOld = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone(); + var oldProfile = clientContext.getProfile(deviceProfile.getUuidId()); if (clientContext.profileUpdate(deviceProfile) != null) { // #1 - JsonArray attributeOld = lwM2MClientProfileOld.getPostAttributeProfile(); - Set attributeSetOld = convertJsonArrayToSet(attributeOld); - JsonArray telemetryOld = lwM2MClientProfileOld.getPostTelemetryProfile(); - Set telemetrySetOld = convertJsonArrayToSet(telemetryOld); - JsonArray observeOld = lwM2MClientProfileOld.getPostObserveProfile(); - JsonObject keyNameOld = lwM2MClientProfileOld.getPostKeyNameProfile(); - JsonObject attributeLwm2mOld = lwM2MClientProfileOld.getPostAttributeLwm2mProfile(); - - LwM2mClientProfile lwM2MClientProfileNew = clientContext.getProfiles().get(deviceProfile.getUuidId()).clone(); - JsonArray attributeNew = lwM2MClientProfileNew.getPostAttributeProfile(); - Set attributeSetNew = convertJsonArrayToSet(attributeNew); - JsonArray telemetryNew = lwM2MClientProfileNew.getPostTelemetryProfile(); - Set telemetrySetNew = convertJsonArrayToSet(telemetryNew); - JsonArray observeNew = lwM2MClientProfileNew.getPostObserveProfile(); - JsonObject keyNameNew = lwM2MClientProfileNew.getPostKeyNameProfile(); - JsonObject attributeLwm2mNew = lwM2MClientProfileNew.getPostAttributeLwm2mProfile(); + Set attributeSetOld = oldProfile.getObserveAttr().getAttribute(); + Set telemetrySetOld = oldProfile.getObserveAttr().getTelemetry(); + Set observeOld = oldProfile.getObserveAttr().getObserve(); + Map keyNameOld = oldProfile.getObserveAttr().getKeyName(); + Map attributeLwm2mOld = oldProfile.getObserveAttr().getAttributeLwm2m(); + + var newProfile = clientContext.getProfile(deviceProfile.getUuidId()); + Set attributeSetNew = newProfile.getObserveAttr().getAttribute(); + Set telemetrySetNew = newProfile.getObserveAttr().getTelemetry(); + Set observeNew = newProfile.getObserveAttr().getObserve(); + Map keyNameNew = newProfile.getObserveAttr().getKeyName(); + Map attributeLwm2mNew = newProfile.getObserveAttr().getAttributeLwm2m(); // #3 - ResultsAnalyzerParameters sendAttrToThingsboard = new ResultsAnalyzerParameters(); + ParametersAnalyzeResult diff = new ParametersAnalyzeResult(); // #3.1 - if (!attributeOld.equals(attributeNew)) { - ResultsAnalyzerParameters postAttributeAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(attributeOld, - new TypeToken>() { - }.getType()), attributeSetNew); - sendAttrToThingsboard.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); - sendAttrToThingsboard.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); + if (!attributeSetOld.equals(attributeSetNew)) { + ParametersAnalyzeResult postAttributeAnalyzer = this.getAnalyzerParameters(attributeSetOld, attributeSetNew); + diff.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); + diff.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); } // #3.2 - if (!telemetryOld.equals(telemetryNew)) { - ResultsAnalyzerParameters postTelemetryAnalyzer = this.getAnalyzerParameters(new Gson().fromJson(telemetryOld, - new TypeToken>() { - }.getType()), telemetrySetNew); - sendAttrToThingsboard.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); - sendAttrToThingsboard.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); + if (!telemetrySetOld.equals(telemetrySetNew)) { + ParametersAnalyzeResult postTelemetryAnalyzer = this.getAnalyzerParameters(telemetrySetOld, telemetrySetNew); + diff.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); + diff.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); } // #3.3 if (!keyNameOld.equals(keyNameNew)) { - ResultsAnalyzerParameters keyNameChange = this.getAnalyzerKeyName(new Gson().fromJson(keyNameOld.toString(), - new TypeToken>() { - }.getType()), - new Gson().fromJson(keyNameNew.toString(), new TypeToken>() { - }.getType())); - sendAttrToThingsboard.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); + ParametersAnalyzeResult keyNameChange = this.getAnalyzerKeyName(keyNameOld, keyNameNew); + diff.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); } // #3.4, #6 @@ -984,32 +975,28 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } // #4.1 add - if (sendAttrToThingsboard.getPathPostParametersAdd().size() > 0) { + if (diff.getPathPostParametersAdd().size() > 0) { // update value in Resources clients.forEach(client -> { - this.readObserveFromProfile(client, sendAttrToThingsboard.getPathPostParametersAdd(), READ); + this.readObserveFromProfile(client, diff.getPathPostParametersAdd(), READ); }); } // #4.2 del - if (sendAttrToThingsboard.getPathPostParametersDel().size() > 0) { - ResultsAnalyzerParameters sendAttrToThingsboardDel = this.getAnalyzerParameters(sendAttrToThingsboard.getPathPostParametersAdd(), sendAttrToThingsboard.getPathPostParametersDel()); - sendAttrToThingsboard.setPathPostParametersDel(sendAttrToThingsboardDel.getPathPostParametersDel()); + if (diff.getPathPostParametersDel().size() > 0) { + ParametersAnalyzeResult sendAttrToThingsboardDel = this.getAnalyzerParameters(diff.getPathPostParametersAdd(), diff.getPathPostParametersDel()); + diff.setPathPostParametersDel(sendAttrToThingsboardDel.getPathPostParametersDel()); } // #5.1 if (!observeOld.equals(observeNew)) { - Set observeSetOld = new Gson().fromJson(observeOld, new TypeToken>() { - }.getType()); - Set observeSetNew = new Gson().fromJson(observeNew, new TypeToken>() { - }.getType()); //#5.2 add // path Attr/Telemetry includes newObserve attributeSetOld.addAll(telemetrySetOld); - ResultsAnalyzerParameters sendObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeSetOld); // add observe + ParametersAnalyzeResult sendObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeOld); // add observe attributeSetNew.addAll(telemetrySetNew); - ResultsAnalyzerParameters sendObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeSetNew); // add observe + ParametersAnalyzeResult sendObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeNew); // add observe // does not include oldObserve - ResultsAnalyzerParameters postObserveAnalyzer = this.getAnalyzerParameters(sendObserveToClientOld.getPathPostParametersAdd(), sendObserveToClientNew.getPathPostParametersAdd()); + ParametersAnalyzeResult postObserveAnalyzer = this.getAnalyzerParameters(sendObserveToClientOld.getPathPostParametersAdd(), sendObserveToClientNew.getPathPostParametersAdd()); // send Request observe to Client clients.forEach(client -> { Registration registration = client.getRegistration(); @@ -1033,10 +1020,10 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param parametersNew - * @return ResultsAnalyzerParameters: add && new */ - private ResultsAnalyzerParameters getAnalyzerParameters(Set parametersOld, Set parametersNew) { - ResultsAnalyzerParameters analyzerParameters = null; + private ParametersAnalyzeResult getAnalyzerParameters(Set parametersOld, Set parametersNew) { + ParametersAnalyzeResult analyzerParameters = null; if (!parametersOld.equals(parametersNew)) { - analyzerParameters = new ResultsAnalyzerParameters(); + analyzerParameters = new ParametersAnalyzeResult(); analyzerParameters.setPathPostParametersAdd(parametersNew .stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet())); analyzerParameters.setPathPostParametersDel(parametersOld @@ -1045,8 +1032,8 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { return analyzerParameters; } - private ResultsAnalyzerParameters getAnalyzerParametersIn(Set parametersObserve, Set parameters) { - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); + private ParametersAnalyzeResult getAnalyzerParametersIn(Set parametersObserve, Set parameters) { + ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult(); analyzerParameters.setPathPostParametersAdd(parametersObserve .stream().filter(parameters::contains).collect(Collectors.toSet())); return analyzerParameters; @@ -1060,7 +1047,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { */ private void readObserveFromProfile(LwM2mClient client, Set targets, LwM2mTypeOper typeOper) { targets.forEach(target -> { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(target)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(target)); if (pathIds.isResource()) { if (READ.equals(typeOper)) { lwM2mTransportRequest.sendAllRequest(client, target, typeOper, @@ -1073,8 +1060,8 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { }); } - private ResultsAnalyzerParameters getAnalyzerKeyName(ConcurrentHashMap keyNameOld, ConcurrentHashMap keyNameNew) { - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); + private ParametersAnalyzeResult getAnalyzerKeyName(Map keyNameOld, Map keyNameNew) { + ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult(); Set paths = keyNameNew.entrySet() .stream() .filter(e -> !e.getValue().equals(keyNameOld.get(e.getKey()))) @@ -1084,23 +1071,12 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } /** - * #3.4, #6 - * #6 * #6.1 - send update WriteAttribute * #6.2 - send empty WriteAttribute * - * @param attributeLwm2mOld - - * @param attributeLwm2mNew - - * @return */ - private void getAnalyzerAttributeLwm2m(List clients, JsonObject attributeLwm2mOld, JsonObject attributeLwm2mNew) { - ResultsAnalyzerParameters analyzerParameters = new ResultsAnalyzerParameters(); - ConcurrentHashMap lwm2mAttributesOld = new Gson().fromJson(attributeLwm2mOld.toString(), - new TypeToken>() { - }.getType()); - ConcurrentHashMap lwm2mAttributesNew = new Gson().fromJson(attributeLwm2mNew.toString(), - new TypeToken>() { - }.getType()); + private void getAnalyzerAttributeLwm2m(List clients, Map lwm2mAttributesOld, Map lwm2mAttributesNew) { + ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult(); Set pathOld = lwm2mAttributesOld.keySet(); Set pathNew = lwm2mAttributesNew.keySet(); analyzerParameters.setPathPostParametersAdd(pathNew @@ -1108,7 +1084,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { analyzerParameters.setPathPostParametersDel(pathOld .stream().filter(p -> !pathNew.contains(p)).collect(Collectors.toSet())); Set pathCommon = pathNew - .stream().filter(p -> pathOld.contains(p)).collect(Collectors.toSet()); + .stream().filter(pathOld::contains).collect(Collectors.toSet()); Set pathCommonChange = pathCommon .stream().filter(p -> !lwm2mAttributesOld.get(p).equals(lwm2mAttributesNew.get(p))).collect(Collectors.toSet()); analyzerParameters.getPathPostParametersAdd().addAll(pathCommonChange); @@ -1120,30 +1096,21 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { Set pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { - ConcurrentHashMap finalParams = lwm2mAttributesNew; - pathSend.forEach(target -> lwM2mTransportRequest.sendAllRequest(client, target, WRITE_ATTRIBUTES, - finalParams.get(target), this.config.getTimeout(), null)); + pathSend.forEach(target -> lwM2mTransportRequest.sendWriteAttributesRequest(client, target, lwm2mAttributesNew.get(target), this.config.getTimeout())); } }); } // #6.2 if (analyzerParameters.getPathPostParametersDel().size() > 0) { clients.forEach(client -> { - Registration registration = client.getRegistration(); Set clientObjects = clientContext.getSupportedIdVerInClient(client); Set pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { - pathSend.forEach(target -> { - Map params = (Map) lwm2mAttributesOld.get(target); - params.clear(); - params.put(OBJECT_VERSION, ""); - lwM2mTransportRequest.sendAllRequest(client, target, WRITE_ATTRIBUTES, params, this.config.getTimeout(), null); - }); + pathSend.forEach(target -> lwM2mTransportRequest.sendWriteAttributesRequest(client, target, new ObjectAttributes(), this.config.getTimeout())); } }); } - } private void cancelObserveFromProfile(LwM2mClient lwM2mClient, Set paramAnallyzer) { @@ -1183,10 +1150,10 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @return - */ public String getPresentPathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { - LwM2mClientProfile profile = clientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); + var profile = clientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); LwM2mClient lwM2mClient = clientContext.getClientBySessionInfo(sessionInfo); - return profile.getPostKeyNameProfile().getAsJsonObject().entrySet().stream() - .filter(e -> e.getValue().getAsString().equals(name) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().map(Map.Entry::getKey) + return profile.getObserveAttr().getKeyName().entrySet().stream() + .filter(e -> e.getValue().equals(name) && validateResourceInModel(lwM2mClient, e.getKey(), false)).findFirst().map(Map.Entry::getKey) .orElse(null); } @@ -1292,7 +1259,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { SessionInfoProto sessionInfo = this.getSessionInfo(lwM2MClient); if (sessionInfo != null) { //#1.1 - ConcurrentMap keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); + Map keyNamesMap = this.getNamesFromProfileForSharedAttributes(lwM2MClient); if (keyNamesMap.values().size() > 0) { try { //#1.2 @@ -1401,25 +1368,15 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { .build(); } - /** - * !!! sharedAttr === profileAttr !!! - * Get names or keyNames from profile: resources IsWritable - * - * @param lwM2MClient - - * @return ArrayList keyNames from profile profileAttr && IsWritable - */ - private ConcurrentMap getNamesFromProfileForSharedAttributes(LwM2mClient lwM2MClient) { - - LwM2mClientProfile profile = clientContext.getProfile(lwM2MClient.getProfileId()); - return new Gson().fromJson(profile.getPostKeyNameProfile().toString(), - new TypeToken>() { - }.getType()); + private Map getNamesFromProfileForSharedAttributes(LwM2mClient lwM2MClient) { + Lwm2mDeviceProfileTransportConfiguration profile = clientContext.getProfile(lwM2MClient.getProfileId()); + 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(convertPathFromIdVerToObjectId(pathIdVer)).getObjectId(); + 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() : 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 fc9290cab0..a1c4227728 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 @@ -20,6 +20,8 @@ import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.Response; import org.eclipse.leshan.core.Link; +import org.eclipse.leshan.core.attributes.Attribute; +import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mNode; import org.eclipse.leshan.core.node.LwM2mObject; @@ -51,6 +53,8 @@ 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.server.common.data.StringUtils; +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; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; @@ -62,15 +66,26 @@ import javax.annotation.PostConstruct; 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.ConcurrentHashMap; 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; 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.eclipse.leshan.core.attributes.Attribute.DIMENSION; +import static org.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; +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.OBJECT_VERSION; +import static org.eclipse.leshan.core.attributes.Attribute.STEP; 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.transport.lwm2m.server.LwM2mTransportServerHelper.getContentFormatByResourceModelType; @@ -91,7 +106,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_REQUEST_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; +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.createWriteAttributeRequest; @@ -116,33 +131,105 @@ public class LwM2mTransportRequest { new NamedThreadFactory(String.format("LwM2M %s channel response after request", RESPONSE_REQUEST_CHANNEL))); } + public void sendReadRequest(LwM2mClient lwM2MClient, String targetId, Long timeout) { + sendReadRequest(lwM2MClient, targetId, lwM2MClient.getDefaultContentFormat(), timeout); + } + + public void sendReadRequest(LwM2mClient lwM2MClient, String targetId, ContentFormat contentFormat, Long timeout) { + String objectId = fromVersionedIdToObjectId(targetId); + if (objectId != null && lwM2MClient.isValidObjectVersion(targetId)) { + sendRequest(lwM2MClient, new ReadRequest(contentFormat, objectId), timeout); + } + } + + public void sendObserveRequest(LwM2mClient lwM2MClient, String targetId, Long timeout) { + sendObserveRequest(lwM2MClient, targetId, lwM2MClient.getDefaultContentFormat(), timeout); + } + + public void sendObserveRequest(LwM2mClient lwM2MClient, String targetId, ContentFormat contentFormat, Long timeout) { + String objectId = fromVersionedIdToObjectId(targetId); + if (objectId != null && lwM2MClient.isValidObjectVersion(targetId)) { + LwM2mPath resultIds = new LwM2mPath(objectId); + Set observations = context.getServer().getObservationService().getObservations(lwM2MClient.getRegistration()); + if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { + ObserveRequest request; + if (resultIds.isResource()) { + request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + } else if (resultIds.isObjectInstance()) { + request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + } else { + request = new ObserveRequest(contentFormat, resultIds.getObjectId()); + } + log.info("[{}] Send observation: {}.", lwM2MClient.getEndpoint(), targetId); + sendRequest(lwM2MClient, request, timeout); + } + } + } + + public void sendDiscoverRequest(LwM2mClient lwM2MClient, String targetId, Long timeout) { + String objectId = fromVersionedIdToObjectId(targetId); + if (objectId != null && lwM2MClient.isValidObjectVersion(targetId)) { + sendRequest(lwM2MClient, new DiscoverRequest(objectId), timeout); + } + } + + public void sendWriteAttributesRequest(LwM2mClient lwM2MClient, String targetId, ObjectAttributes params, Long timeout) { + String objectId = fromVersionedIdToObjectId(targetId); + if (objectId != null && lwM2MClient.isValidObjectVersion(targetId) && params != null) { + 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(lwM2MClient, new WriteAttributesRequest(objectId, attributeSet), timeout); + } + } + + private void addAttribute(List attributes, String attributeName, T value) { + addAttribute(attributes, attributeName, value, null, null); + } + + private void addAttribute(List attributes, String attributeName, T value, Function converter) { + addAttribute(attributes, attributeName, value, null, converter); + } + + private void addAttribute(List attributes, String attributeName, T value, Predicate filter, Function converter) { + if (value != null && ((filter == null) || filter.test(value))) { + attributes.add(new Attribute(attributeName, converter != null ? converter.apply(value) : value)); + } + } + public void sendAllRequest(LwM2mClient lwM2MClient, String targetIdVer, LwM2mTypeOper typeOper, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { sendAllRequest(lwM2MClient, targetIdVer, typeOper, lwM2MClient.getDefaultContentFormat(), params, timeoutInMs, lwm2mClientRpcRequest); } - public void sendAllRequest(LwM2mClient lwM2MClient, String targetIdVer, LwM2mTypeOper typeOper, ContentFormat contentFormat, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { Registration registration = lwM2MClient.getRegistration(); try { - String target = convertPathFromIdVerToObjectId(targetIdVer); - if(contentFormat == null){ + String target = fromVersionedIdToObjectId(targetIdVer); + if (contentFormat == null) { contentFormat = ContentFormat.DEFAULT; } LwM2mPath resultIds = target != null ? new LwM2mPath(target) : null; - if (!OBSERVE_CANCEL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0 && lwM2MClient != null) { + if (!OBSERVE_CANCEL.name().equals(typeOper.name()) && resultIds != null && registration != null && resultIds.getObjectId() >= 0) { if (lwM2MClient.isValidObjectVersion(targetIdVer)) { timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; SimpleDownlinkRequest request = createRequest(registration, lwM2MClient, typeOper, contentFormat, target, targetIdVer, resultIds, params, lwm2mClientRpcRequest); if (request != null) { try { - this.sendRequest(registration, lwM2MClient, request, timeoutInMs, lwm2mClientRpcRequest); + this.sendRequest(lwM2MClient, request, timeoutInMs, lwm2mClientRpcRequest); } catch (ClientSleepingException e) { SimpleDownlinkRequest finalRequest = request; long finalTimeoutInMs = timeoutInMs; LwM2mClientRpcRequest finalRpcRequest = lwm2mClientRpcRequest; - lwM2MClient.getQueuedRequests().add(() -> sendRequest(registration, lwM2MClient, finalRequest, finalTimeoutInMs, finalRpcRequest)); + lwM2MClient.getQueuedRequests().add(() -> sendRequest(lwM2MClient, finalRequest, finalTimeoutInMs, finalRpcRequest)); } catch (Exception e) { log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e); } @@ -222,8 +309,8 @@ public class LwM2mTransportRequest { } private SimpleDownlinkRequest createRequest(Registration registration, LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, - ContentFormat contentFormat, String target, String targetIdVer, - LwM2mPath resultIds, Object params, LwM2mClientRpcRequest rpcRequest) { + ContentFormat contentFormat, String target, String targetIdVer, + LwM2mPath resultIds, Object params, LwM2mClientRpcRequest rpcRequest) { SimpleDownlinkRequest request = null; switch (typeOper) { case READ: @@ -320,15 +407,18 @@ public class LwM2mTransportRequest { return request; } - /** - * @param registration - - * @param request - - * @param timeoutInMs - - */ + private void sendRequest(LwM2mClient lwM2MClient, SimpleDownlinkRequest request, long timeoutInMs) { + try { + sendRequest(lwM2MClient, request, timeoutInMs, null); + } catch (ClientSleepingException e) { + //TODO: this may cause infinite loop / memory leak. + lwM2MClient.getQueuedRequests().add(() -> sendRequest(lwM2MClient, request, timeoutInMs, null)); + } + } @SuppressWarnings({"error sendRequest"}) - private void sendRequest(Registration registration, LwM2mClient lwM2MClient, SimpleDownlinkRequest request, - long timeoutInMs, LwM2mClientRpcRequest rpcRequest) { + private void sendRequest(LwM2mClient lwM2MClient, SimpleDownlinkRequest request, long timeoutInMs, LwM2mClientRpcRequest rpcRequest) { + Registration registration = lwM2MClient.getRegistration(); context.getServer().send(registration, request, timeoutInMs, (ResponseCallback) response -> { if (!lwM2MClient.isInit()) { @@ -455,8 +545,8 @@ public class LwM2mTransportRequest { /** * processing a response from a client * - * @param path - - * @param response - + * @param path - + * @param response - */ private void sendResponse(LwM2mClient lwM2mClient, String path, LwM2mResponse response, SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { @@ -550,8 +640,7 @@ public class LwM2mTransportRequest { if (rpcRequest != null) { rpcRequest.setInfoMsg(msg); } - } - else if (rpcRequest != null) { + } else if (rpcRequest != null) { handler.sentRpcResponse(rpcRequest, response.getCode().getName(), msg, LOG_LW2M_INFO); } } @@ -610,4 +699,5 @@ public class LwM2mTransportRequest { handler.sentRpcResponse(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO); } } + } 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 6159a201b4..dd77209e24 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 @@ -16,13 +16,9 @@ package org.thingsboard.server.transport.lwm2m.server; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.collect.Sets; -import com.google.gson.Gson; -import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; 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.leshan.core.attributes.Attribute; @@ -42,17 +38,17 @@ import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.registration.Registration; import org.nustaq.serialization.FSTConfiguration; import org.thingsboard.server.common.data.DeviceProfile; +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.id.TenantId; 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.LwM2mClientProfile; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; @@ -60,7 +56,6 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; @@ -379,7 +374,7 @@ public class LwM2mTransportUtil { switch (updateResultFw) { case INITIAL: return equalsFwSateToFirmwareUpdateStatus(stateFw); - case UPDATE_SUCCESSFULLY: + case UPDATE_SUCCESSFULLY: return UPDATED; case NOT_ENOUGH: case OUT_OFF_MEMORY: @@ -399,7 +394,7 @@ public class LwM2mTransportUtil { switch (updateResultFw) { case INITIAL: return VERIFIED; - case UPDATE_SUCCESSFULLY: + case UPDATE_SUCCESSFULLY: return UPDATED; case NOT_ENOUGH: case OUT_OFF_MEMORY: @@ -695,26 +690,23 @@ public class LwM2mTransportUtil { } } - public static LwM2mOtaConvert convertOtaUpdateValueToString (String pathIdVer, Object value, ResourceModel.Type currentType) { - String path = convertPathFromIdVerToObjectId(pathIdVer); + public static LwM2mOtaConvert convertOtaUpdateValueToString(String pathIdVer, Object value, ResourceModel.Type currentType) { + String path = fromVersionedIdToObjectId(pathIdVer); LwM2mOtaConvert lwM2mOtaConvert = new LwM2mOtaConvert(); if (path != null) { if (FW_STATE_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); lwM2mOtaConvert.setValue(StateFw.fromStateFwByCode(((Long) value).intValue()).type); return lwM2mOtaConvert; - } - else if (FW_RESULT_ID.equals(path)) { + } else if (FW_RESULT_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); lwM2mOtaConvert.setValue(UpdateResultFw.fromUpdateResultFwByCode(((Long) value).intValue()).type); return lwM2mOtaConvert; - } - else if (SW_UPDATE_STATE_ID.equals(path)) { + } else if (SW_UPDATE_STATE_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); lwM2mOtaConvert.setValue(UpdateStateSw.fromUpdateStateSwByCode(((Long) value).intValue()).type); return lwM2mOtaConvert; - } - else if (SW_RESULT_ID.equals(path)) { + } else if (SW_RESULT_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); lwM2mOtaConvert.setValue(UpdateResultSw.fromUpdateResultSwByCode(((Long) value).intValue()).type); return lwM2mOtaConvert; @@ -738,113 +730,32 @@ 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; - } - - /** - * @return deviceProfileBody with Observe&Attribute&Telemetry From Thingsboard - * Example: - * property: {"clientLwM2mSettings": { - * clientUpdateValueAfterConnect: false; - * } - * property: "observeAttr" - * {"keyName": { - * "/3/0/1": "modelNumber", - * "/3/0/0": "manufacturer", - * "/3/0/2": "serialNumber" - * }, - * "attribute":["/2/0/1","/3/0/9"], - * "telemetry":["/1/0/1","/2/0/1","/6/0/1"], - * "observe":["/2/0","/2/0/0","/4/0/2"]} - * "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"}, - * "/3_1.0/0": {"gt": 17}, - * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}} - */ - //TODO: refactor this to valid jackson code instead of parsing manually. - public static LwM2mClientProfile toLwM2MClientProfile(DeviceProfile deviceProfile) { -// if (((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { -// Object profile = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); -// try { -// ObjectMapper mapper = new ObjectMapper(); -// String profileStr = mapper.writeValueAsString(profile); -// JsonObject profileJson = (profileStr != null) ? validateJson(profileStr) : null; -// return getValidateCredentialsBodyFromThingsboard(profileJson) ? LwM2mTransportUtil.getNewProfileParameters(profileJson, deviceProfile.getTenantId()) : null; -// } catch (IOException e) { -// log.error("", e); -// } -// } - return null; - } - - public static JsonObject getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) { -// if (deviceProfile != null && ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties().size() > 0) { -// Object bootstrap = ((Lwm2mDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration()).getProperties(); -// try { -// ObjectMapper mapper = new ObjectMapper(); -// String bootstrapStr = mapper.writeValueAsString(bootstrap); -// JsonObject objectMsg = (bootstrapStr != null) ? validateJson(bootstrapStr) : null; -// return (getValidateBootstrapProfileFromThingsboard(objectMsg)) ? objectMsg.get(BOOTSTRAP).getAsJsonObject() : null; -// } catch (IOException e) { -// log.error("", e); -// } -// } - return null; - } - - private static boolean getValidateCredentialsBodyFromThingsboard(JsonObject objectMsg) { - return (objectMsg != null && - !objectMsg.isJsonNull() && - objectMsg.has(CLIENT_LWM2M_SETTINGS) && - !objectMsg.get(CLIENT_LWM2M_SETTINGS).isJsonNull() && - objectMsg.get(CLIENT_LWM2M_SETTINGS).isJsonObject() && - objectMsg.has(OBSERVE_ATTRIBUTE_TELEMETRY) && - !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).isJsonNull() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).isJsonObject() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(KEY_NAME) && - !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).isJsonNull() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(KEY_NAME).isJsonObject() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().has(ATTRIBUTE) && - !objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).isJsonNull() && - objectMsg.get(OBSERVE_ATTRIBUTE_TELEMETRY).getAsJsonObject().get(ATTRIBUTE).isJsonArray() && - 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_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()); +// 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)) { + return (Lwm2mDeviceProfileTransportConfiguration) transportConfiguration; + } else { + log.warn("[{}] Received profile with invalid transport configuration: {}", deviceProfile.getId(), deviceProfile.getProfileData().getTransportConfiguration()); + throw new IllegalArgumentException("Received profile with invalid transport configuration: " + transportConfiguration.getType()); + } } - private static boolean getValidateBootstrapProfileFromThingsboard(JsonObject objectMsg) { - return (objectMsg != null && - !objectMsg.isJsonNull() && - objectMsg.has(BOOTSTRAP) && - objectMsg.get(BOOTSTRAP).isJsonObject() && - !objectMsg.get(BOOTSTRAP).isJsonNull() && - objectMsg.get(BOOTSTRAP).getAsJsonObject().has(SERVERS) && - !objectMsg.get(BOOTSTRAP).getAsJsonObject().get(SERVERS).isJsonNull() && - objectMsg.get(BOOTSTRAP).getAsJsonObject().get(SERVERS).isJsonObject() && - objectMsg.get(BOOTSTRAP).getAsJsonObject().has(BOOTSTRAP_SERVER) && - !objectMsg.get(BOOTSTRAP).getAsJsonObject().get(BOOTSTRAP_SERVER).isJsonNull() && - objectMsg.get(BOOTSTRAP).getAsJsonObject().get(BOOTSTRAP_SERVER).isJsonObject() && - objectMsg.get(BOOTSTRAP).getAsJsonObject().has(LWM2M_SERVER) && - !objectMsg.get(BOOTSTRAP).getAsJsonObject().get(LWM2M_SERVER).isJsonNull() && - objectMsg.get(BOOTSTRAP).getAsJsonObject().get(LWM2M_SERVER).isJsonObject()); + public static BootstrapConfiguration getBootstrapParametersFromThingsboard(DeviceProfile deviceProfile) { + return toLwM2MClientProfile(deviceProfile).getBootstrap(); } - public static JsonObject validateJson(String jsonStr) { JsonObject object = null; if (jsonStr != null && !jsonStr.isEmpty()) { @@ -901,8 +812,11 @@ public class LwM2mTransportUtil { }; } - public static String convertPathFromIdVerToObjectId(String pathIdVer) { + public static String fromVersionedIdToObjectId(String pathIdVer) { try { + if(pathIdVer == null) { + return null; + } 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]; @@ -911,7 +825,8 @@ public class LwM2mTransportUtil { return pathIdVer; } } catch (Exception e) { - return null; + log.warn("Issue converting path with version [{}] to path without version: ", pathIdVer, e); + throw new RuntimeException(e); } } @@ -1025,12 +940,6 @@ public class LwM2mTransportUtil { return attributeLists.toArray(Attribute[]::new); } - public static Set convertJsonArrayToSet(JsonArray jsonArray) { - List attributeListOld = new Gson().fromJson(jsonArray, new TypeToken>() { - }.getType()); - return Sets.newConcurrentHashSet(attributeListOld); - } - public static ResourceModel.Type equalsResourceTypeGetSimpleName(Object value) { switch (value.getClass().getSimpleName()) { case "Double": 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 2d981e0d4e..650726604f 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 @@ -72,7 +72,7 @@ public class LwM2mVersionedModelProvider implements LwM2mModelProvider { public DynamicModel(Registration registration) { this.registration = registration; - this.tenantId = lwM2mClientContext.getProfile(registration).getTenantId(); + this.tenantId = lwM2mClientContext.getClientByEndpoint(registration.getEndpoint()).getTenantId(); } @Override 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 519f32b076..a6586da0cc 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 @@ -32,6 +32,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.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; @@ -58,7 +59,7 @@ 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.convertPathFromIdVerToObjectId; +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.getVerFromPathIdVerOrId; @@ -92,6 +93,8 @@ public class LwM2mClient implements Cloneable { @Getter private SecurityInfo securityInfo; @Getter + private TenantId tenantId; + @Getter private UUID deviceId; @Getter private SessionInfoProto session; @@ -127,19 +130,17 @@ public class LwM2mClient implements Cloneable { this.state = LwM2MClientState.CREATED; } - public void init(String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID profileId, UUID sessionId) { + public void init(String identity, SecurityInfo securityInfo, ValidateDeviceCredentialsResponse credentials, UUID sessionId) { this.identity = identity; this.securityInfo = securityInfo; this.credentials = credentials; - this.profileId = profileId; this.init = false; - 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(); - this.deviceProfileName = session.getDeviceType(); - } + this.session = createSession(nodeId, sessionId, credentials); + this.tenantId = new TenantId(new UUID(session.getTenantIdMSB(), session.getTenantIdLSB())); + 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 lock() { @@ -198,7 +199,7 @@ public class LwM2mClient implements Cloneable { this.resources.get(pathRezIdVer).setLwM2mResource(rez); return true; } else { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); if (resourceModel != null) { this.resources.put(pathRezIdVer, new ResourceValue(rez, resourceModel)); @@ -226,7 +227,7 @@ public class LwM2mClient implements Cloneable { } public String getRezIdByResourceNameAndObjectInstanceId(String resourceName, String pathObjectInstanceIdVer, LwM2mModelProvider modelProvider) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathObjectInstanceIdVer)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathObjectInstanceIdVer)); if (pathIds.isObjectInstance()) { Set rezIds = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources.entrySet() @@ -240,7 +241,7 @@ public class LwM2mClient implements Cloneable { } public ResourceModel getResourceModel(String pathIdVer, LwM2mModelProvider modelProvider) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verRez = getVerFromPathIdVerOrId(pathIdVer); return verRez == null || verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) @@ -248,7 +249,7 @@ public class LwM2mClient implements Cloneable { } public ObjectModel getObjectModel(String pathIdVer, LwM2mModelProvider modelProvider) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathIdVer)); String verSupportedObject = registration.getSupportedObject().get(pathIds.getObjectId()); String verRez = getVerFromPathIdVerOrId(pathIdVer); return verRez == null || verRez.equals(verSupportedObject) ? modelProvider.getObjectModel(registration) @@ -290,13 +291,13 @@ public class LwM2mClient implements Cloneable { } else { return String.format("LwM2mSingleResource [id=%s, value=%s, type=%s]", lwM2mResource.getId(), converter.convertValue(lwM2mResource.getValue(), - OPAQUE, STRING, new LwM2mPath(convertPathFromIdVerToObjectId(pathIdVer))), lwM2mResource.getType().name()); + OPAQUE, STRING, new LwM2mPath(fromVersionedIdToObjectId(pathIdVer))), lwM2mResource.getType().name()); } } public Collection getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, LwM2mValueConverterImpl converter) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources; @@ -312,7 +313,7 @@ public class LwM2mClient implements Cloneable { public Collection getNewResourcesForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, LwM2mValueConverterImpl converter) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRezIdVer)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRezIdVer)); Collection resources = ConcurrentHashMap.newKeySet(); Map resourceModels = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources; @@ -328,7 +329,7 @@ public class LwM2mClient implements Cloneable { } public boolean isValidObjectVersion(String path) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(path)); + 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); @@ -341,7 +342,7 @@ public class LwM2mClient implements Cloneable { public void deleteResources(String pathIdVer, LwM2mModelProvider modelProvider) { Set key = getKeysEqualsIdVer(pathIdVer); key.forEach(pathRez -> { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRez)); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); if (resourceModel != null) { this.resources.get(pathRez).setResourceModel(resourceModel); @@ -361,7 +362,7 @@ public class LwM2mClient implements Cloneable { } private void saveResourceModel(String pathRez, LwM2mModelProvider modelProvider) { - LwM2mPath pathIds = new LwM2mPath(convertPathFromIdVerToObjectId(pathRez)); + LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(pathRez)); ResourceModel resourceModel = modelProvider.getObjectModel(registration).getResourceModel(pathIds.getObjectId(), pathIds.getResourceId()); this.resources.get(pathRez).setResourceModel(resourceModel); } @@ -395,16 +396,16 @@ public class LwM2mClient implements Cloneable { public LwM2mFwSwUpdate getFwUpdate (LwM2mClientContext clientContext) { if (this.fwUpdate == null) { - LwM2mClientProfile lwM2mClientProfile = clientContext.getProfile(this.getProfileId()); - this.fwUpdate = new LwM2mFwSwUpdate(this, OtaPackageType.FIRMWARE, lwM2mClientProfile.getFwUpdateStrategy()); + var profile = clientContext.getProfile(this.getProfileId()); + this.fwUpdate = new LwM2mFwSwUpdate(this, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy()); } return this.fwUpdate; } public LwM2mFwSwUpdate getSwUpdate (LwM2mClientContext clientContext) { if (this.swUpdate == null) { - LwM2mClientProfile lwM2mClientProfile = clientContext.getProfile(this.getProfileId()); - this.swUpdate = new LwM2mFwSwUpdate(this, OtaPackageType.SOFTWARE, lwM2mClientProfile.getSwUpdateStrategy()); + var profile = clientContext.getProfile(this.getProfileId()); + this.swUpdate = new LwM2mFwSwUpdate(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/LwM2mClientContext.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientContext.java index 613a8b7971..f0539a0ccc 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,11 +17,12 @@ 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.Map; import java.util.Set; import java.util.UUID; @@ -41,15 +42,12 @@ public interface LwM2mClientContext { Collection getLwM2mClients(); - Map getProfiles(); + //TODO: replace UUID with DeviceProfileId + Lwm2mDeviceProfileTransportConfiguration getProfile(UUID profileUuId); - LwM2mClientProfile getProfile(UUID profileUuId); + Lwm2mDeviceProfileTransportConfiguration getProfile(Registration registration); - LwM2mClientProfile getProfile(Registration registration); - - Map setProfiles(Map profiles); - - LwM2mClientProfile profileUpdate(DeviceProfile deviceProfile); + Lwm2mDeviceProfileTransportConfiguration profileUpdate(DeviceProfile deviceProfile); Set getSupportedIdVerInClient(LwM2mClient registration); 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 c088ac8477..736722ddc4 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.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; @@ -50,7 +51,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { private final TbEditableSecurityStore securityStore; private final Map lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); private final Map lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); - private Map profiles = new ConcurrentHashMap<>(); + private final Map profiles = new ConcurrentHashMap<>(); @Override public LwM2mClient getClientByEndpoint(String endpoint) { @@ -67,11 +68,11 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { TbLwM2MSecurityInfo securityInfo = securityStore.getTbLwM2MSecurityInfoByEndpoint(lwM2MClient.getEndpoint()); if (securityInfo.getSecurityMode() != null) { if (securityInfo.getDeviceProfile() != null) { - UUID profileUuid = profileUpdate(securityInfo.getDeviceProfile()) != null ? securityInfo.getDeviceProfile().getUuidId() : null; + profileUpdate(securityInfo.getDeviceProfile()); if (securityInfo.getSecurityInfo() != null) { - lwM2MClient.init(securityInfo.getSecurityInfo().getIdentity(), securityInfo.getSecurityInfo(), securityInfo.getMsg(), profileUuid, UUID.randomUUID()); + lwM2MClient.init(securityInfo.getSecurityInfo().getIdentity(), securityInfo.getSecurityInfo(), securityInfo.getMsg(), UUID.randomUUID()); } else if (NO_SEC.equals(securityInfo.getSecurityMode())) { - lwM2MClient.init(null, null, securityInfo.getMsg(), profileUuid, UUID.randomUUID()); + lwM2MClient.init(null, null, securityInfo.getMsg(), UUID.randomUUID()); } else { throw new RuntimeException(String.format("Registration failed: device %s not found.", lwM2MClient.getEndpoint())); } @@ -157,7 +158,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { @Override public void registerClient(Registration registration, ValidateDeviceCredentialsResponse credentials) { LwM2mClient client = getClientByEndpoint(registration.getEndpoint()); - client.init(null, null, credentials, credentials.getDeviceProfile().getUuidId(), UUID.randomUUID()); + client.init(null, null, credentials, UUID.randomUUID()); lwM2mClientsByRegistrationId.put(registration.getId(), client); profileUpdate(credentials.getDeviceProfile()); } @@ -168,35 +169,20 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } @Override - public Map getProfiles() { - return profiles; - } - - @Override - public LwM2mClientProfile getProfile(UUID profileId) { + public Lwm2mDeviceProfileTransportConfiguration getProfile(UUID profileId) { return profiles.get(profileId); } @Override - public LwM2mClientProfile getProfile(Registration registration) { - return this.getProfiles().get(getClientByEndpoint(registration.getEndpoint()).getProfileId()); + public Lwm2mDeviceProfileTransportConfiguration getProfile(Registration registration) { + return profiles.get(getClientByEndpoint(registration.getEndpoint()).getProfileId()); } @Override - public Map setProfiles(Map profiles) { - return this.profiles = profiles; - } - - @Override - public LwM2mClientProfile profileUpdate(DeviceProfile deviceProfile) { - LwM2mClientProfile lwM2MClientProfile = deviceProfile != null ? - LwM2mTransportUtil.toLwM2MClientProfile(deviceProfile) : null; - if (lwM2MClientProfile != null) { - profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); - return lwM2MClientProfile; - } else { - return null; - } + public Lwm2mDeviceProfileTransportConfiguration profileUpdate(DeviceProfile deviceProfile) { + Lwm2mDeviceProfileTransportConfiguration lwM2MClientProfile = LwM2mTransportUtil.toLwM2MClientProfile(deviceProfile); + profiles.put(deviceProfile.getUuidId(), lwM2MClientProfile); + return lwM2MClientProfile; } @Override diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java deleted file mode 100644 index 67edbf2747..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientProfile.java +++ /dev/null @@ -1,113 +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 com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; -import lombok.Data; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MClientStrategy; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2MSoftwareUpdateStrategy; - -@Data -public class LwM2mClientProfile { - private final String clientStrategyStr = "clientStrategy"; - private final String fwUpdateStrategyStr = "fwUpdateStrategy"; - private final String swUpdateStrategyStr = "swUpdateStrategy"; - - private TenantId tenantId; - /** - * "clientLwM2mSettings": { - * "fwUpdateStrategy": "1", - * "swUpdateStrategy": "1", - * "clientStrategy": "1" - * } - **/ - private JsonObject postClientLwM2mSettings; - - /** - * {"keyName": { - * "/3_1.0/0/1": "modelNumber", - * "/3_1.0/0/0": "manufacturer", - * "/3_1.0/0/2": "serialNumber" - * } - **/ - private JsonObject postKeyNameProfile; - - /** - * [ "/3_1.0/0/0", "/3_1.0/0/1"] - */ - private JsonArray postAttributeProfile; - - /** - * [ "/3_1.0/0/0", "/3_1.0/0/2"] - */ - private JsonArray postTelemetryProfile; - - /** - * [ "/3_1.0/0", "/3_1.0/0/1, "/3_1.0/0/2"] - */ - private JsonArray postObserveProfile; - - /** - * "attributeLwm2m": {"/3_1.0": {"ver": "currentTimeTest11"}, - * "/3_1.0/0": {"gt": 17}, - * "/3_1.0/0/9": {"pmax": 45}, "/3_1.2": {ver": "3_1.2"}} - */ - private JsonObject postAttributeLwm2mProfile; - - public LwM2mClientProfile clone() { - LwM2mClientProfile lwM2mClientProfile = new LwM2mClientProfile(); - lwM2mClientProfile.postClientLwM2mSettings = this.deepCopy(this.postClientLwM2mSettings, JsonObject.class); - lwM2mClientProfile.postKeyNameProfile = this.deepCopy(this.postKeyNameProfile, JsonObject.class); - lwM2mClientProfile.postAttributeProfile = this.deepCopy(this.postAttributeProfile, JsonArray.class); - lwM2mClientProfile.postTelemetryProfile = this.deepCopy(this.postTelemetryProfile, JsonArray.class); - lwM2mClientProfile.postObserveProfile = this.deepCopy(this.postObserveProfile, JsonArray.class); - lwM2mClientProfile.postAttributeLwm2mProfile = this.deepCopy(this.postAttributeLwm2mProfile, JsonObject.class); - return lwM2mClientProfile; - } - - - private T deepCopy(T elements, Class type) { - try { - Gson gson = new Gson(); - return gson.fromJson(gson.toJson(elements), type); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - public int getClientStrategy() { - return this.postClientLwM2mSettings.getAsJsonObject().has(this.clientStrategyStr) ? - Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.clientStrategyStr).getAsString()) : - LwM2MClientStrategy.CLIENT_STRATEGY_1.code; - } - - public int getFwUpdateStrategy() { - return this.postClientLwM2mSettings.getAsJsonObject().has(this.fwUpdateStrategyStr) ? - Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.fwUpdateStrategyStr).getAsString()) : - LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY.code; - } - - public int getSwUpdateStrategy() { - return this.postClientLwM2mSettings.getAsJsonObject().has(this.swUpdateStrategyStr) ? - Integer.parseInt(this.postClientLwM2mSettings.getAsJsonObject().get(this.swUpdateStrategyStr).getAsString()) : - LwM2MSoftwareUpdateStrategy.BINARY.code; - } -} 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 53385b6f61..e11d50f29f 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 @@ -52,7 +52,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.S import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.START_JSON_KEY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.TARGET_ID_VER_KEY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.VALUE_KEY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromIdVerToObjectId; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validPathIdVer; @Slf4j @@ -152,8 +152,8 @@ public class LwM2mClientRpcRequest { else if (this.getTargetIdVer() != null && (EXECUTE == this.getTypeOper() || WRITE_REPLACE == this.getTypeOper()) - && !(new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(this.getTargetIdVer()))).isResource() - || new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(this.getTargetIdVer()))).isResourceInstance())) { + && !(new LwM2mPath(Objects.requireNonNull(fromVersionedIdToObjectId(this.getTargetIdVer()))).isResource() + || new LwM2mPath(Objects.requireNonNull(fromVersionedIdToObjectId(this.getTargetIdVer()))).isResourceInstance())) { this.setErrorMsg("Invalid parameter " + TARGET_ID_VER_KEY + ". Only Resource or ResourceInstance can be this operation"); } @@ -247,7 +247,7 @@ public class LwM2mClientRpcRequest { private ConcurrentHashMap convertParamsToResourceId(ConcurrentHashMap params, DefaultLwM2MUplinkMsgHandler serviceImpl) { Map paramsIdVer = new ConcurrentHashMap<>(); - LwM2mPath targetId = new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(this.targetIdVer))); + LwM2mPath targetId = new LwM2mPath(Objects.requireNonNull(fromVersionedIdToObjectId(this.targetIdVer))); if (targetId.isObjectInstance()) { params.forEach((k, v) -> { try { @@ -256,7 +256,7 @@ public class LwM2mClientRpcRequest { } catch (NumberFormatException e) { String targetIdVer = serviceImpl.getPresentPathIntoProfile(sessionInfo, k); if (targetIdVer != null) { - LwM2mPath lwM2mPath = new LwM2mPath(Objects.requireNonNull(convertPathFromIdVerToObjectId(targetIdVer))); + LwM2mPath lwM2mPath = new LwM2mPath(Objects.requireNonNull(fromVersionedIdToObjectId(targetIdVer))); paramsIdVer.put(String.valueOf(lwM2mPath.getResourceId()), v); } /** WRITE_UPDATE*/ diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAnalyzerParameters.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ParametersAnalyzeResult.java similarity index 92% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAnalyzerParameters.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ParametersAnalyzeResult.java index 45b52811d8..9cf304ab97 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResultsAnalyzerParameters.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ParametersAnalyzeResult.java @@ -21,11 +21,11 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @Data -public class ResultsAnalyzerParameters { +public class ParametersAnalyzeResult { Set pathPostParametersAdd; Set pathPostParametersDel; - public ResultsAnalyzerParameters() { + public ParametersAnalyzeResult() { this.pathPostParametersAdd = ConcurrentHashMap.newKeySet(); this.pathPostParametersDel = ConcurrentHashMap.newKeySet(); } From 898c937e5d4a1e753049d9d1b764ded6c4f1f660 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Thu, 10 Jun 2021 10:49:32 +0300 Subject: [PATCH 031/231] Removed redundant endpoint, RPK test is separated --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 3 +- ...Test.java => PskLwm2mIntegrationTest.java} | 20 ++-------- .../lwm2m/RpkLwM2MIntegrationTest.java | 39 +++++++++++++++++++ .../lwm2m/X509LwM2MIntegrationTest.java | 8 ++-- 4 files changed, 47 insertions(+), 23 deletions(-) rename application/src/test/java/org/thingsboard/server/transport/lwm2m/{PskRpkLwm2mIntegrationTest.java => PskLwm2mIntegrationTest.java} (61%) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.java 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 39b4d5ef2f..f223bb079b 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 @@ -163,13 +163,12 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { protected static final int SECURE_PORT = 5686; protected static final NetworkConfig SECURE_COAP_CONFIG = new NetworkConfig().setString("COAP_SECURE_PORT", Integer.toString(SECURE_PORT)); - protected static final String SECURE_ENDPOINT = "deviceAEndpoint"; + protected static final String ENDPOINT = "deviceAEndpoint"; protected static final String SECURE_URI = "coaps://localhost:" + SECURE_PORT; protected static final int PORT = 5685; protected static final Security SECURITY = noSec("coap://localhost:" + PORT, 123); protected static final NetworkConfig COAP_CONFIG = new NetworkConfig().setString("COAP_PORT", Integer.toString(PORT)); - protected static final String ENDPOINT = "deviceAEndpoint"; public AbstractLwM2MIntegrationTest() { // create client credentials diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/PskRpkLwm2mIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/PskLwm2mIntegrationTest.java similarity index 61% rename from application/src/test/java/org/thingsboard/server/transport/lwm2m/PskRpkLwm2mIntegrationTest.java rename to application/src/test/java/org/thingsboard/server/transport/lwm2m/PskLwm2mIntegrationTest.java index 0d2f4d05a5..6d3e544d57 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/PskRpkLwm2mIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/PskLwm2mIntegrationTest.java @@ -19,39 +19,25 @@ import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; import org.junit.Test; import org.thingsboard.server.common.data.device.credentials.lwm2m.PSKClientCredentials; -import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; import java.nio.charset.StandardCharsets; import static org.eclipse.leshan.client.object.Security.psk; -import static org.eclipse.leshan.client.object.Security.rpk; -public class PskRpkLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest { - @Test - public void testConnectWithRPKAndObserveTelemetry() throws Exception { - RPKClientCredentials rpkClientCredentials = new RPKClientCredentials(); - rpkClientCredentials.setEndpoint(SECURE_ENDPOINT); - rpkClientCredentials.setKey(Hex.encodeHexString(clientPublicKey.getEncoded())); - Security security = rpk(SECURE_URI, - 123, - clientPublicKey.getEncoded(), - clientPrivateKey.getEncoded(), - serverX509Cert.getPublicKey().getEncoded()); - super.basicTestConnectionObserveTelemetry(security, rpkClientCredentials, SECURE_COAP_CONFIG, SECURE_ENDPOINT); - } +public class PskLwm2mIntegrationTest extends AbstractLwM2MIntegrationTest { @Test public void testConnectWithPSKAndObserveTelemetry() throws Exception { String pskIdentity = "SOME_PSK_ID"; String pskKey = "73656372657450534b"; PSKClientCredentials clientCredentials = new PSKClientCredentials(); - clientCredentials.setEndpoint(SECURE_ENDPOINT); + clientCredentials.setEndpoint(ENDPOINT); clientCredentials.setKey(pskKey); clientCredentials.setIdentity(pskIdentity); Security security = psk(SECURE_URI, 123, pskIdentity.getBytes(StandardCharsets.UTF_8), Hex.decodeHex(pskKey.toCharArray())); - super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, SECURE_ENDPOINT); + super.basicTestConnectionObserveTelemetry(security, clientCredentials, SECURE_COAP_CONFIG, ENDPOINT); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.java new file mode 100644 index 0000000000..eb81f91e2c --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.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; + +import org.eclipse.leshan.client.object.Security; +import org.eclipse.leshan.core.util.Hex; +import org.junit.Test; +import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCredentials; + +import static org.eclipse.leshan.client.object.Security.rpk; + +public class RpkLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + @Test + public void testConnectWithRPKAndObserveTelemetry() throws Exception { + RPKClientCredentials rpkClientCredentials = new RPKClientCredentials(); + rpkClientCredentials.setEndpoint(ENDPOINT); + rpkClientCredentials.setKey(Hex.encodeHexString(clientPublicKey.getEncoded())); + Security security = rpk(SECURE_URI, + 123, + clientPublicKey.getEncoded(), + clientPrivateKey.getEncoded(), + serverX509Cert.getPublicKey().getEncoded()); + super.basicTestConnectionObserveTelemetry(security, rpkClientCredentials, SECURE_COAP_CONFIG, ENDPOINT); + } + +} 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 8e3c26298d..9158acbc6c 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 @@ -26,25 +26,25 @@ public class X509LwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { @Test public void testConnectAndObserveTelemetry() throws Exception { X509ClientCredentials credentials = new X509ClientCredentials(); - credentials.setEndpoint(SECURE_ENDPOINT); + credentials.setEndpoint(ENDPOINT); Security security = x509(SECURE_URI, 123, clientX509Cert.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); - super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, SECURE_ENDPOINT); + super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); } @Test public void testConnectWithCertAndObserveTelemetry() throws Exception { X509ClientCredentials credentials = new X509ClientCredentials(); - credentials.setEndpoint(SECURE_ENDPOINT); + credentials.setEndpoint(ENDPOINT); credentials.setCert(SslUtil.getCertificateString(clientX509CertNotTrusted)); Security security = x509(SECURE_URI, 123, clientX509CertNotTrusted.getEncoded(), clientPrivateKeyFromCert.getEncoded(), serverX509Cert.getEncoded()); - super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, SECURE_ENDPOINT); + super.basicTestConnectionObserveTelemetry(security, credentials, SECURE_COAP_CONFIG, ENDPOINT); } } From 32cd80957aebdc45700a5ddbfbc74e0de9c793c6 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 10 Jun 2021 13:19:17 +0300 Subject: [PATCH 032/231] Fix profile update --- .../server/DefaultLwM2MUplinkMsgHandler.java | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java index 7653a2c099..5eeba5a109 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java @@ -39,6 +39,7 @@ 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.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.OtaPackageKey; @@ -936,18 +937,20 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { var oldProfile = clientContext.getProfile(deviceProfile.getUuidId()); if (clientContext.profileUpdate(deviceProfile) != null) { // #1 - Set attributeSetOld = oldProfile.getObserveAttr().getAttribute(); - Set telemetrySetOld = oldProfile.getObserveAttr().getTelemetry(); - Set observeOld = oldProfile.getObserveAttr().getObserve(); - Map keyNameOld = oldProfile.getObserveAttr().getKeyName(); - Map attributeLwm2mOld = oldProfile.getObserveAttr().getAttributeLwm2m(); + TelemetryMappingConfiguration oldTelemetryParams = oldProfile.getObserveAttr(); + Set attributeSetOld = oldTelemetryParams.getAttribute(); + Set telemetrySetOld = oldTelemetryParams.getTelemetry(); + Set observeOld = oldTelemetryParams.getObserve(); + Map keyNameOld = oldTelemetryParams.getKeyName(); + Map attributeLwm2mOld = oldTelemetryParams.getAttributeLwm2m(); var newProfile = clientContext.getProfile(deviceProfile.getUuidId()); - Set attributeSetNew = newProfile.getObserveAttr().getAttribute(); - Set telemetrySetNew = newProfile.getObserveAttr().getTelemetry(); - Set observeNew = newProfile.getObserveAttr().getObserve(); - Map keyNameNew = newProfile.getObserveAttr().getKeyName(); - Map attributeLwm2mNew = newProfile.getObserveAttr().getAttributeLwm2m(); + TelemetryMappingConfiguration newTelemetryParams = newProfile.getObserveAttr(); + Set attributeSetNew = newTelemetryParams.getAttribute(); + Set telemetrySetNew = newTelemetryParams.getTelemetry(); + Set observeNew = newTelemetryParams.getObserve(); + Map keyNameNew = newTelemetryParams.getKeyName(); + Map attributeLwm2mNew = newTelemetryParams.getAttributeLwm2m(); // #3 ParametersAnalyzeResult diff = new ParametersAnalyzeResult(); @@ -981,11 +984,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { this.readObserveFromProfile(client, diff.getPathPostParametersAdd(), READ); }); } - // #4.2 del - if (diff.getPathPostParametersDel().size() > 0) { - ParametersAnalyzeResult sendAttrToThingsboardDel = this.getAnalyzerParameters(diff.getPathPostParametersAdd(), diff.getPathPostParametersDel()); - diff.setPathPostParametersDel(sendAttrToThingsboardDel.getPathPostParametersDel()); - } // #5.1 if (!observeOld.equals(observeNew)) { From 05df5e03efbaff6a1a5b48d27dc6327962ecd4b2 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 10 Jun 2021 11:26:33 +0300 Subject: [PATCH 033/231] Revert "js-executor: upgraded libraries to fix vulnerability warning (@google-cloud/pubsub", amqplib)" Untested version with breaking changes declared This reverts commit 80f83a86 --- msa/js-executor/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index ba940ee85a..187144da20 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -13,8 +13,8 @@ }, "dependencies": { "@azure/service-bus": "^1.1.9", - "@google-cloud/pubsub": "^2.12.0", - "amqplib": "^0.8.0", + "@google-cloud/pubsub": "^2.5.0", + "amqplib": "^0.6.0", "aws-sdk": "^2.741.0", "azure-sb": "^0.11.1", "config": "^3.3.1", From 3a31b9c5ea7451dce8e5936e196f9fdbbb75eb4c Mon Sep 17 00:00:00 2001 From: Kien Truong Date: Thu, 3 Jun 2021 11:05:02 +0700 Subject: [PATCH 034/231] Fix wrong configuration key for Compression Type Fix #4678 (cherry picked from commit c176ca94aa0f612c3bb3856ae2acf3a399cc74a4) --- msa/js-executor/queue/kafkaTemplate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index b34727c977..7e19f373a8 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -26,7 +26,7 @@ const acks = Number(config.get('kafka.acks')); const maxBatchSize = Number(config.get('kafka.batch_size')); const linger = Number(config.get('kafka.linger_ms')); const requestTimeout = Number(config.get('kafka.requestTimeout')); -const compressionType = (config.get('kafka.requestTimeout') === "gzip") ? CompressionTypes.GZIP : CompressionTypes.None; +const compressionType = (config.get('kafka.compression') === "gzip") ? CompressionTypes.GZIP : CompressionTypes.None; let kafkaClient; let kafkaAdmin; From 9a4a94621a88dd7e6e329dc1d7767ff75e311e16 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 10 Jun 2021 14:31:51 +0300 Subject: [PATCH 035/231] js-executor: parameter added for Kafka PARTITIONS_CONSUMED_CONCURRENTLY to decrease max latency while scale down replicas --- 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 7773fdaf0c..34903c4541 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -29,6 +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:128}" # for producer linger_ms: "${TB_KAFKA_LINGER_MS:1}" # for producer + partitions_consumed_concurrently: "${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 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 bcefc1c639..190cf097ed 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -29,6 +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 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" diff --git a/msa/js-executor/queue/kafkaTemplate.js b/msa/js-executor/queue/kafkaTemplate.js index 7e19f373a8..46ded135e6 100644 --- a/msa/js-executor/queue/kafkaTemplate.js +++ b/msa/js-executor/queue/kafkaTemplate.js @@ -27,6 +27,7 @@ const maxBatchSize = Number(config.get('kafka.batch_size')); const linger = Number(config.get('kafka.linger_ms')); const requestTimeout = Number(config.get('kafka.requestTimeout')); const compressionType = (config.get('kafka.compression') === "gzip") ? CompressionTypes.GZIP : CompressionTypes.None; +const partitionsConsumedConcurrently = Number(config.get('kafka.partitions_consumed_concurrently')); let kafkaClient; let kafkaAdmin; @@ -197,7 +198,7 @@ async function sendMessagesAsBatch(isImmediately) { logger.info('Started ThingsBoard JavaScript Executor Microservice.'); await consumer.run({ - //partitionsConsumedConcurrently: 1, // Default: 1 + partitionsConsumedConcurrently: partitionsConsumedConcurrently, eachMessage: async ({topic, partition, message}) => { let headers = message.headers; let key = message.key; From 9ffa50f4a528d3f7b99e167e3bd797213ed26314 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 10 Jun 2021 16:05:50 +0300 Subject: [PATCH 036/231] js-executor: fixed env variables mapping --- msa/js-executor/config/custom-environment-variables.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index 34903c4541..4185e65835 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -27,9 +27,9 @@ 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 - batch_size: "${TB_KAFKA_BATCH_SIZE:128}" # for producer - linger_ms: "${TB_KAFKA_LINGER_MS:1}" # for producer - partitions_consumed_concurrently: "${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 + 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 requestTimeout: "TB_QUEUE_KAFKA_REQUEST_TIMEOUT_MS" compression: "TB_QUEUE_KAFKA_COMPRESSION" # gzip or uncompressed topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" From f7ab3a21b2abbee796dd9291df290955284780ed Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 10 Jun 2021 16:11:48 +0300 Subject: [PATCH 037/231] LwM2M Refactoring --- .../server/DefaultLwM2MUplinkMsgHandler.java | 151 ++++++------------ ...va => DefaultLwM2mDownlinkMsgHandler.java} | 2 +- .../lwm2m/server/LwM2mDownlinkMsgHandler.java | 4 + .../lwm2m/server/client/LwM2mFwSwUpdate.java | 14 +- 4 files changed, 61 insertions(+), 110 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{LwM2mTransportRequest.java => DefaultLwM2mDownlinkMsgHandler.java} (99%) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java index 5eeba5a109..e6dc83bc44 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java @@ -132,20 +132,20 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private final LwM2MJsonAdaptor adaptor; private final TbLwM2MDtlsSessionStore sessionStore; public final LwM2mClientContext clientContext; - public final LwM2mTransportRequest lwM2mTransportRequest; + public final DefaultLwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler; private final Map rpcSubscriptions; public final Map firmwareUpdateState; public DefaultLwM2MUplinkMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, LwM2mClientContext clientContext, - @Lazy LwM2mTransportRequest lwM2mTransportRequest, + @Lazy DefaultLwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler, OtaPackageDataCache otaPackageDataCache, LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) { this.transportService = transportService; this.config = config; this.helper = helper; this.clientContext = clientContext; - this.lwM2mTransportRequest = lwM2mTransportRequest; + this.defaultLwM2MDownlinkMsgHandler = defaultLwM2MDownlinkMsgHandler; this.otaPackageDataCache = otaPackageDataCache; this.context = context; this.adaptor = adaptor; @@ -291,7 +291,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (registration != null) { LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); if (client != null && client.getRegistration() != null && client.getRegistration().getId().equals(registration.getId())) { - this.lwM2mTransportRequest.sendAllRequest(client, null, OBSERVE_CANCEL_ALL, + this.defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, null, OBSERVE_CANCEL_ALL, null, null, this.config.getTimeout(), null); } } @@ -467,7 +467,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); } else { - lwM2mTransportRequest.sendAllRequest(client, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), + defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), null, lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), this.config.getTimeout(), lwm2mClientRpcRequest); @@ -595,7 +595,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == profile.getClientLwM2mSettings().getClientOnlyObserveAfterConnect()) { // #2 lwM2MClient.getPendingReadRequests().addAll(supportedObjects); - supportedObjects.forEach(path -> lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, READ, + supportedObjects.forEach(path -> defaultLwM2MDownlinkMsgHandler.sendAllRequest(lwM2MClient, path, READ, null, this.config.getTimeout(), null)); } // #1 @@ -612,7 +612,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { targetIds.addAll(profile.getObserveAttr().getTelemetry()); targetIds = targetIds.stream().filter(target -> isSupportedTargetId(supportedObjects, target)).collect(Collectors.toSet()); lwM2MClient.getPendingReadRequests().addAll(targetIds); - targetIds.forEach(targetId -> lwM2mTransportRequest.sendReadRequest(lwM2MClient, targetId, this.config.getTimeout())); + targetIds.forEach(targetId -> defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, targetId, this.config.getTimeout())); } private void sendObserveRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { @@ -620,7 +620,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { 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 -> lwM2mTransportRequest.sendObserveRequest(lwM2MClient, targetId, this.config.getTimeout())); + targetIds.forEach(targetId -> defaultLwM2MDownlinkMsgHandler.sendObserveRequest(lwM2MClient, targetId, this.config.getTimeout())); } private void sendWriteAttributeRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { @@ -628,7 +628,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { attributesMap = attributesMap.entrySet().stream().filter(target -> isSupportedTargetId(supportedObjects, target.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); // TODO: why do we need to put observe into pending read requests? // lwM2MClient.getPendingReadRequests().addAll(targetIds); - attributesMap.forEach((targetId, params) -> lwM2mTransportRequest.sendWriteAttributesRequest(lwM2MClient, targetId, params, this.config.getTimeout())); + attributesMap.forEach((targetId, params) -> defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(lwM2MClient, targetId, params, this.config.getTimeout())); } @@ -637,7 +637,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { 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 -> lwM2mTransportRequest.sendDiscoverRequest(lwM2MClient, targetId, this.config.getTimeout())); + targetIds.forEach(targetId -> defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, targetId, this.config.getTimeout())); } private void sendRequestsToClient(LwM2mClient lwM2MClient, LwM2mTypeOper operationType, Set supportedObjectIds, Set desiredObjectIds, ConcurrentHashMap params) { @@ -649,7 +649,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { lwM2MClient.getPendingReadRequests().addAll(targetObjectIds); targetObjectIds.forEach(target -> { Object additionalParams = params != null ? params.get(target) : null; - lwM2mTransportRequest.sendAllRequest(lwM2MClient, target, operationType, additionalParams, this.config.getTimeout(), null); + defaultLwM2MDownlinkMsgHandler.sendAllRequest(lwM2MClient, target, operationType, additionalParams, this.config.getTimeout(), null); }); if (OBSERVE.equals(operationType)) { lwM2MClient.initReadValue(this, null); @@ -702,17 +702,17 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * set setClient_fw_info... = value **/ if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { - lwM2MClient.getFwUpdate().initReadValue(this, this.lwM2mTransportRequest, path); + lwM2MClient.getFwUpdate().initReadValue(this, this.defaultLwM2MDownlinkMsgHandler, path); } if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { - lwM2MClient.getSwUpdate().initReadValue(this, this.lwM2mTransportRequest, path); + 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(clientContext); log.warn("93) path: [{}] value: [{}]", path, lwM2mResource.getValue()); - fwUpdate.updateStateOta(this, lwM2mTransportRequest, registration, path, ((Long) lwM2mResource.getValue()).intValue()); + fwUpdate.updateStateOta(this, defaultLwM2MDownlinkMsgHandler, registration, path, ((Long) lwM2mResource.getValue()).intValue()); } this.updateAttrTelemetry(registration, Collections.singleton(path)); } else { @@ -907,31 +907,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } - /** - * #1 Read new, old Value (Attribute, Telemetry, Observe, KeyName) - * #2 Update in lwM2MClient: ...Profile if changes from update device - * #3 Equivalence test: old <> new Value (Attribute, Telemetry, Observe, KeyName) - * #3.1 Attribute isChange (add&del) - * #3.2 Telemetry isChange (add&del) - * #3.3 KeyName isChange (add) - * #3.4 attributeLwm2m isChange (update WrightAttribute: add/update/del) - * #4 update - * #4.1 add If #3 isChange, then analyze and update Value in Transport form Client and send Value to thingsboard - * #4.2 del - * -- if add attributes includes del telemetry - result del for observe - * #5 - * #5.1 Observe isChange (add&del) - * #5.2 Observe.add - * -- path Attr/Telemetry includes newObserve and does not include oldObserve: send Request observe to Client - * #5.3 Observe.del - * -- different between newObserve and oldObserve: send Request cancel observe to client - * #6 - * #6.1 - update WriteAttribute - * #6.2 - del WriteAttribute - * - * @param clients - - * @param deviceProfile - - */ //TODO: review and optimize the logic to minimize number of the requests to device. private void onDeviceProfileUpdate(List clients, DeviceProfile deviceProfile) { var oldProfile = clientContext.getProfile(deviceProfile.getUuidId()); @@ -952,82 +927,54 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { Map keyNameNew = newTelemetryParams.getKeyName(); Map attributeLwm2mNew = newTelemetryParams.getAttributeLwm2m(); - // #3 - ParametersAnalyzeResult diff = new ParametersAnalyzeResult(); + Set observeToAdd = diffSets(observeOld, observeNew); + Set observeToRemove = diffSets(observeNew, observeOld); + + Set newObjectsToRead = new HashSet<>(); + // #3.1 if (!attributeSetOld.equals(attributeSetNew)) { - ParametersAnalyzeResult postAttributeAnalyzer = this.getAnalyzerParameters(attributeSetOld, attributeSetNew); - diff.getPathPostParametersAdd().addAll(postAttributeAnalyzer.getPathPostParametersAdd()); - diff.getPathPostParametersDel().addAll(postAttributeAnalyzer.getPathPostParametersDel()); + newObjectsToRead.addAll(diffSets(attributeSetOld, attributeSetNew)); } // #3.2 if (!telemetrySetOld.equals(telemetrySetNew)) { - ParametersAnalyzeResult postTelemetryAnalyzer = this.getAnalyzerParameters(telemetrySetOld, telemetrySetNew); - diff.getPathPostParametersAdd().addAll(postTelemetryAnalyzer.getPathPostParametersAdd()); - diff.getPathPostParametersDel().addAll(postTelemetryAnalyzer.getPathPostParametersDel()); + newObjectsToRead.addAll(diffSets(telemetrySetOld, telemetrySetNew)); } // #3.3 if (!keyNameOld.equals(keyNameNew)) { ParametersAnalyzeResult keyNameChange = this.getAnalyzerKeyName(keyNameOld, keyNameNew); - diff.getPathPostParametersAdd().addAll(keyNameChange.getPathPostParametersAdd()); + newObjectsToRead.addAll(keyNameChange.getPathPostParametersAdd()); } // #3.4, #6 if (!attributeLwm2mOld.equals(attributeLwm2mNew)) { - this.getAnalyzerAttributeLwm2m(clients, attributeLwm2mOld, attributeLwm2mNew); + this.compareAndSendWriteAttributes(clients, attributeLwm2mOld, attributeLwm2mNew); } // #4.1 add - if (diff.getPathPostParametersAdd().size() > 0) { + if (!newObjectsToRead.isEmpty()) { + Set newObjectsToReadButNotNewInObserve = diffSets(observeToAdd, newObjectsToRead); // update value in Resources - clients.forEach(client -> { - this.readObserveFromProfile(client, diff.getPathPostParametersAdd(), READ); - }); + if(!newObjectsToReadButNotNewInObserve.isEmpty()) { + clients.forEach(client -> this.readObserveFromProfile(client, newObjectsToReadButNotNewInObserve, READ)); + } } - // #5.1 - if (!observeOld.equals(observeNew)) { - //#5.2 add - // path Attr/Telemetry includes newObserve - attributeSetOld.addAll(telemetrySetOld); - ParametersAnalyzeResult sendObserveToClientOld = this.getAnalyzerParametersIn(attributeSetOld, observeOld); // add observe - attributeSetNew.addAll(telemetrySetNew); - ParametersAnalyzeResult sendObserveToClientNew = this.getAnalyzerParametersIn(attributeSetNew, observeNew); // add observe - // does not include oldObserve - ParametersAnalyzeResult postObserveAnalyzer = this.getAnalyzerParameters(sendObserveToClientOld.getPathPostParametersAdd(), sendObserveToClientNew.getPathPostParametersAdd()); - // send Request observe to Client - clients.forEach(client -> { - Registration registration = client.getRegistration(); - if (postObserveAnalyzer.getPathPostParametersAdd().size() > 0) { - this.readObserveFromProfile(client, postObserveAnalyzer.getPathPostParametersAdd(), OBSERVE); - } - // 5.3 del - // send Request cancel observe to Client - if (postObserveAnalyzer.getPathPostParametersDel().size() > 0) { - this.cancelObserveFromProfile(client, postObserveAnalyzer.getPathPostParametersDel()); - } - }); + // Calculating difference between old and new flags. + if(!observeToAdd.isEmpty()){ + clients.forEach(client -> this.readObserveFromProfile(client, observeToAdd, OBSERVE)); + } + if(!observeToRemove.isEmpty()){ + clients.forEach(client -> this.cancelObserveFromProfile(client, observeToRemove)); } } } /** - * Compare old list with new list after change AttrTelemetryObserve in config Profile - * - * @param parametersOld - - * @param parametersNew - - * @return ResultsAnalyzerParameters: add && new + Returns new set with elements that are present in set B(new) but absent in set A(old). */ - private ParametersAnalyzeResult getAnalyzerParameters(Set parametersOld, Set parametersNew) { - ParametersAnalyzeResult analyzerParameters = null; - if (!parametersOld.equals(parametersNew)) { - analyzerParameters = new ParametersAnalyzeResult(); - analyzerParameters.setPathPostParametersAdd(parametersNew - .stream().filter(p -> !parametersOld.contains(p)).collect(Collectors.toSet())); - analyzerParameters.setPathPostParametersDel(parametersOld - .stream().filter(p -> !parametersNew.contains(p)).collect(Collectors.toSet())); - } - return analyzerParameters; + private static Set diffSets(Set a, Set b){ + return b.stream().filter(p -> !a.contains(p)).collect(Collectors.toSet()); } private ParametersAnalyzeResult getAnalyzerParametersIn(Set parametersObserve, Set parameters) { @@ -1048,10 +995,10 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(target)); if (pathIds.isResource()) { if (READ.equals(typeOper)) { - lwM2mTransportRequest.sendAllRequest(client, target, typeOper, + defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, target, typeOper, null, this.config.getTimeout(), null); } else if (OBSERVE.equals(typeOper)) { - lwM2mTransportRequest.sendAllRequest(client, target, typeOper, + defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, target, typeOper, null, this.config.getTimeout(), null); } } @@ -1073,7 +1020,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * #6.2 - send empty WriteAttribute * */ - private void getAnalyzerAttributeLwm2m(List clients, Map lwm2mAttributesOld, Map lwm2mAttributesNew) { + private void compareAndSendWriteAttributes(List clients, Map lwm2mAttributesOld, Map lwm2mAttributesNew) { ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult(); Set pathOld = lwm2mAttributesOld.keySet(); Set pathNew = lwm2mAttributesNew.keySet(); @@ -1094,7 +1041,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { Set pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { - pathSend.forEach(target -> lwM2mTransportRequest.sendWriteAttributesRequest(client, target, lwm2mAttributesNew.get(target), this.config.getTimeout())); + pathSend.forEach(target -> defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(client, target, lwm2mAttributesNew.get(target), this.config.getTimeout())); } }); } @@ -1105,7 +1052,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { Set pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { - pathSend.forEach(target -> lwM2mTransportRequest.sendWriteAttributesRequest(client, target, new ObjectAttributes(), this.config.getTimeout())); + pathSend.forEach(target -> defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(client, target, new ObjectAttributes(), this.config.getTimeout())); } }); } @@ -1114,7 +1061,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private void cancelObserveFromProfile(LwM2mClient lwM2mClient, Set paramAnallyzer) { paramAnallyzer.forEach(pathIdVer -> { if (this.getResourceValueFromLwM2MClient(lwM2mClient, pathIdVer) != null) { - lwM2mTransportRequest.sendAllRequest(lwM2mClient, pathIdVer, OBSERVE_CANCEL, null, this.config.getTimeout(), null); + defaultLwM2MDownlinkMsgHandler.sendAllRequest(lwM2mClient, pathIdVer, OBSERVE_CANCEL, null, this.config.getTimeout(), null); } } ); @@ -1122,7 +1069,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) { if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { - lwM2mTransportRequest.sendAllRequest(lwM2MClient, path, WRITE_REPLACE, valueNew, this.config.getTimeout(), null); + defaultLwM2MDownlinkMsgHandler.sendAllRequest(lwM2MClient, path, WRITE_REPLACE, valueNew, 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", @@ -1293,9 +1240,9 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { fwUpdate.setCurrentTitle(response.getTitle()); fwUpdate.setCurrentId(new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB())); if (rpcRequest == null) { - fwUpdate.sendReadObserveInfo(lwM2mTransportRequest); + fwUpdate.sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); } else { - fwUpdate.writeFwSwWare(handler, lwM2mTransportRequest); + fwUpdate.writeFwSwWare(handler, defaultLwM2MDownlinkMsgHandler); } } else { String msgError = String.format("OtaPackage device: %s, version: %s, stateUpdate: %s", @@ -1336,11 +1283,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { 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(lwM2mTransportRequest); + lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); if (rpcRequest == null) { - lwM2MClient.getSwUpdate().sendReadObserveInfo(lwM2mTransportRequest); + lwM2MClient.getSwUpdate().sendReadObserveInfo(defaultLwM2MDownlinkMsgHandler); } else { - lwM2MClient.getSwUpdate().writeFwSwWare(handler, lwM2mTransportRequest); + lwM2MClient.getSwUpdate().writeFwSwWare(handler, defaultLwM2MDownlinkMsgHandler); } } else { log.trace("Software [{}] [{}]", lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); 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/DefaultLwM2mDownlinkMsgHandler.java similarity index 99% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mTransportRequest.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java index a1c4227728..30fee78d1a 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/DefaultLwM2mDownlinkMsgHandler.java @@ -114,7 +114,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c @Service @TbLwM2mTransportComponent @RequiredArgsConstructor -public class LwM2mTransportRequest { +public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { private ExecutorService responseRequestExecutor; public LwM2mValueConverterImpl converter; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java new file mode 100644 index 0000000000..0beb8ea7cb --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java @@ -0,0 +1,4 @@ +package org.thingsboard.server.transport.lwm2m.server; + +public interface LwM2mDownlinkMsgHandler { +} 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 a13253282e..56a601b3d2 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 @@ -25,7 +25,7 @@ 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.DefaultLwM2MUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportRequest; +import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.util.ArrayList; @@ -156,7 +156,7 @@ public class LwM2mFwSwUpdate { } } - public void initReadValue(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request, String pathIdVer) { + public void initReadValue(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request, String pathIdVer) { if (pathIdVer != null) { this.pendingInfoRequestsStart.remove(pathIdVer); } @@ -176,7 +176,7 @@ public class LwM2mFwSwUpdate { * Send FsSw to Lwm2mClient: * before operation Write: fw_state = DOWNLOADING */ - public void writeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request) { + public void writeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request) { if (this.currentId != null) { this.stateUpdate = OtaPackageUpdateStatus.INITIATED.name(); this.sendLogs(handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); @@ -230,7 +230,7 @@ public class LwM2mFwSwUpdate { * fw_state/sw_state = UPDATING * send execute */ - public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request) { + public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request) { this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); request.sendAllRequest(this.lwM2MClient, this.pathInstallId, EXECUTE, null, 0, this.rpcRequest); } @@ -381,7 +381,7 @@ public class LwM2mFwSwUpdate { return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult; } - private void observeStateUpdate(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request) { + private void observeStateUpdate(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request) { request.sendAllRequest(lwM2MClient, convertPathFromObjectIdToIdVer(this.pathStateId, this.lwM2MClient.getRegistration()), OBSERVE, null, null, 0, null); @@ -401,7 +401,7 @@ public class LwM2mFwSwUpdate { } } - public void sendReadObserveInfo(LwM2mTransportRequest request) { + public void sendReadObserveInfo(DefaultLwM2mDownlinkMsgHandler request) { this.infoFwSwUpdate = true; this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( this.pathStateId, this.lwM2MClient.getRegistration())); @@ -433,7 +433,7 @@ public class LwM2mFwSwUpdate { * - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully") * - finished operation Execute (FwUpdate) */ - public void updateStateOta(DefaultLwM2MUplinkMsgHandler handler, LwM2mTransportRequest request, + public void updateStateOta(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request, Registration registration, String path, int value) { if (OBJ_5_BINARY.code == this.getUpdateStrategy()) { if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path))) { From 7e3087e107f67b59597b4d94e18b218fa8fe3325 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 10 Jun 2021 19:40:13 +0300 Subject: [PATCH 038/231] First iteration of downlink handler refactoring --- .../server/DefaultLwM2MUplinkMsgHandler.java | 97 +---- .../DefaultLwM2mDownlinkMsgHandler.java | 393 ++++++++++++------ .../lwm2m/server/LwM2mDownlinkMsgHandler.java | 44 ++ .../server/LwM2mTransportServerHelper.java | 23 +- .../lwm2m/server/client/LwM2mClient.java | 6 +- .../lwm2m/server/client/LwM2mFwSwUpdate.java | 38 +- .../downlink/DownlinkRequestCallback.java | 25 ++ .../lwm2m/utils/LwM2mValueConverterImpl.java | 2 +- 8 files changed, 369 insertions(+), 259 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java index e6dc83bc44..45e07eaf4c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java @@ -132,13 +132,13 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private final LwM2MJsonAdaptor adaptor; private final TbLwM2MDtlsSessionStore sessionStore; public final LwM2mClientContext clientContext; - public final DefaultLwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler; + public final LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler; private final Map rpcSubscriptions; public final Map firmwareUpdateState; public DefaultLwM2MUplinkMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, LwM2mClientContext clientContext, - @Lazy DefaultLwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler, + @Lazy LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler, OtaPackageDataCache otaPackageDataCache, LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) { this.transportService = transportService; @@ -291,8 +291,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (registration != null) { LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); if (client != null && client.getRegistration() != null && client.getRegistration().getId().equals(registration.getId())) { - this.defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, null, OBSERVE_CANCEL_ALL, - null, null, this.config.getTimeout(), null); + defaultLwM2MDownlinkMsgHandler.sendCancelAllRequest(client, this.config.getTimeout(), null); } } } @@ -595,8 +594,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == profile.getClientLwM2mSettings().getClientOnlyObserveAfterConnect()) { // #2 lwM2MClient.getPendingReadRequests().addAll(supportedObjects); - supportedObjects.forEach(path -> defaultLwM2MDownlinkMsgHandler.sendAllRequest(lwM2MClient, path, READ, - null, this.config.getTimeout(), null)); + supportedObjects.forEach(path -> defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, path, this.config.getTimeout())); } // #1 this.sendReadRequests(lwM2MClient, profile, supportedObjects); @@ -640,24 +638,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { targetIds.forEach(targetId -> defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, targetId, this.config.getTimeout())); } - private void sendRequestsToClient(LwM2mClient lwM2MClient, LwM2mTypeOper operationType, Set supportedObjectIds, Set desiredObjectIds, ConcurrentHashMap params) { - if (desiredObjectIds != null && !desiredObjectIds.isEmpty()) { - Set targetObjectIds = desiredObjectIds.stream().filter(target -> isSupportedTargetId(supportedObjectIds, target) - ).collect(Collectors.toUnmodifiableSet()); - if (!targetObjectIds.isEmpty()) { - //TODO: remove this side effect? - lwM2MClient.getPendingReadRequests().addAll(targetObjectIds); - targetObjectIds.forEach(target -> { - Object additionalParams = params != null ? params.get(target) : null; - defaultLwM2MDownlinkMsgHandler.sendAllRequest(lwM2MClient, target, operationType, additionalParams, this.config.getTimeout(), null); - }); - if (OBSERVE.equals(operationType)) { - lwM2MClient.initReadValue(this, null); - } - } - } - } - /** * @param registration - * @param lwM2mObject - @@ -955,56 +935,32 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (!newObjectsToRead.isEmpty()) { Set newObjectsToReadButNotNewInObserve = diffSets(observeToAdd, newObjectsToRead); // update value in Resources - if(!newObjectsToReadButNotNewInObserve.isEmpty()) { - clients.forEach(client -> this.readObserveFromProfile(client, newObjectsToReadButNotNewInObserve, READ)); + for (String targetId : newObjectsToReadButNotNewInObserve) { + clients.forEach(client -> defaultLwM2MDownlinkMsgHandler.sendReadRequest(client, targetId, this.config.getTimeout())); } } // Calculating difference between old and new flags. - if(!observeToAdd.isEmpty()){ - clients.forEach(client -> this.readObserveFromProfile(client, observeToAdd, OBSERVE)); + if (!observeToAdd.isEmpty()) { + for (String targetId : observeToAdd) { + clients.forEach(client -> defaultLwM2MDownlinkMsgHandler.sendObserveRequest(client, targetId, this.config.getTimeout())); + } } - if(!observeToRemove.isEmpty()){ - clients.forEach(client -> this.cancelObserveFromProfile(client, observeToRemove)); + if (!observeToRemove.isEmpty()) { + for (String targetId : observeToRemove) { + clients.forEach(client -> defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, targetId, this.config.getTimeout(), null)); + } } } } /** - Returns new set with elements that are present in set B(new) but absent in set A(old). + Returns new set with elements that are present in set B(new) but absent in set A(old). */ - private static Set diffSets(Set a, Set b){ + private static Set diffSets(Set a, Set b) { return b.stream().filter(p -> !a.contains(p)).collect(Collectors.toSet()); } - private ParametersAnalyzeResult getAnalyzerParametersIn(Set parametersObserve, Set parameters) { - ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult(); - analyzerParameters.setPathPostParametersAdd(parametersObserve - .stream().filter(parameters::contains).collect(Collectors.toSet())); - return analyzerParameters; - } - - /** - * Update Resource value after change RezAttrTelemetry in config Profile - * send response Read to Client and add path to pathResAttrTelemetry in LwM2MClient.getAttrTelemetryObserveValue() - * - * @param targets - path Resources == [ "/2/0/0", "/2/0/1"] - */ - private void readObserveFromProfile(LwM2mClient client, Set targets, LwM2mTypeOper typeOper) { - targets.forEach(target -> { - LwM2mPath pathIds = new LwM2mPath(fromVersionedIdToObjectId(target)); - if (pathIds.isResource()) { - if (READ.equals(typeOper)) { - defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, target, typeOper, - null, this.config.getTimeout(), null); - } else if (OBSERVE.equals(typeOper)) { - defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, target, typeOper, - null, this.config.getTimeout(), null); - } - } - }); - } - private ParametersAnalyzeResult getAnalyzerKeyName(Map keyNameOld, Map keyNameNew) { ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult(); Set paths = keyNameNew.entrySet() @@ -1058,24 +1014,15 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } } - private void cancelObserveFromProfile(LwM2mClient lwM2mClient, Set paramAnallyzer) { - paramAnallyzer.forEach(pathIdVer -> { - if (this.getResourceValueFromLwM2MClient(lwM2mClient, pathIdVer) != null) { - defaultLwM2MDownlinkMsgHandler.sendAllRequest(lwM2mClient, pathIdVer, OBSERVE_CANCEL, null, this.config.getTimeout(), null); - } - } - ); - } - - private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object valueNew, String path) { - if (valueNew != null && (valueOld == null || !valueNew.toString().equals(valueOld.toString()))) { - defaultLwM2MDownlinkMsgHandler.sendAllRequest(lwM2MClient, path, WRITE_REPLACE, valueNew, this.config.getTimeout(), null); + private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String path) { + if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { + defaultLwM2MDownlinkMsgHandler.sendWriteReplaceRequest(lwM2MClient, path, newValue, this.config.getTimeout(), null); } else { - log.error("Failed update resource [{}] [{}]", path, valueNew); + log.error("Failed update resource [{}] [{}]", path, newValue); String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", - LOG_LW2M_ERROR, path, valueNew); + LOG_LW2M_ERROR, path, newValue); this.sendLogsToThingsboard(lwM2MClient, logMsg); - log.info("Failed update resource [{}] [{}]", path, valueNew); + log.info("Failed update resource [{}] [{}]", path, newValue); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java index 30fee78d1a..3ba3de2b75 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java @@ -30,6 +30,7 @@ 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.node.codec.CodecException; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.request.DeleteRequest; @@ -53,13 +54,13 @@ 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.server.common.data.StringUtils; 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; 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.server.downlink.DownlinkRequestCallback; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; @@ -79,16 +80,13 @@ 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.eclipse.leshan.core.attributes.Attribute.DIMENSION; import static org.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; 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.OBJECT_VERSION; import static org.eclipse.leshan.core.attributes.Attribute.STEP; 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.transport.lwm2m.server.LwM2mTransportServerHelper.getContentFormatByResourceModelType; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEFAULT_TIMEOUT; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_5_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_UPDATE_ID; @@ -106,9 +104,9 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESPONSE_REQUEST_CHANNEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID; -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.createWriteAttributeRequest; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; @Slf4j @Service @@ -131,26 +129,30 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { new NamedThreadFactory(String.format("LwM2M %s channel response after request", RESPONSE_REQUEST_CHANNEL))); } - public void sendReadRequest(LwM2mClient lwM2MClient, String targetId, Long timeout) { - sendReadRequest(lwM2MClient, targetId, lwM2MClient.getDefaultContentFormat(), timeout); + @Override + public void sendReadRequest(LwM2mClient client, String targetId, Long timeout) { + sendReadRequest(client, targetId, client.getDefaultContentFormat(), timeout); } - public void sendReadRequest(LwM2mClient lwM2MClient, String targetId, ContentFormat contentFormat, Long timeout) { + @Override + public void sendReadRequest(LwM2mClient client, String targetId, ContentFormat contentFormat, Long timeout) { String objectId = fromVersionedIdToObjectId(targetId); - if (objectId != null && lwM2MClient.isValidObjectVersion(targetId)) { - sendRequest(lwM2MClient, new ReadRequest(contentFormat, objectId), timeout); + if (objectId != null && client.isValidObjectVersion(targetId)) { + sendRequest(client, new ReadRequest(contentFormat, objectId), timeout); } } - public void sendObserveRequest(LwM2mClient lwM2MClient, String targetId, Long timeout) { - sendObserveRequest(lwM2MClient, targetId, lwM2MClient.getDefaultContentFormat(), timeout); + @Override + public void sendObserveRequest(LwM2mClient client, String targetId, Long timeout) { + sendObserveRequest(client, targetId, client.getDefaultContentFormat(), timeout); } - public void sendObserveRequest(LwM2mClient lwM2MClient, String targetId, ContentFormat contentFormat, Long timeout) { + @Override + public void sendObserveRequest(LwM2mClient client, String targetId, ContentFormat contentFormat, Long timeout) { String objectId = fromVersionedIdToObjectId(targetId); - if (objectId != null && lwM2MClient.isValidObjectVersion(targetId)) { + if (objectId != null && client.isValidObjectVersion(targetId)) { LwM2mPath resultIds = new LwM2mPath(objectId); - Set observations = context.getServer().getObservationService().getObservations(lwM2MClient.getRegistration()); + Set observations = context.getServer().getObservationService().getObservations(client.getRegistration()); if (observations.stream().noneMatch(observation -> observation.getPath().equals(resultIds))) { ObserveRequest request; if (resultIds.isResource()) { @@ -160,22 +162,68 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } else { request = new ObserveRequest(contentFormat, resultIds.getObjectId()); } - log.info("[{}] Send observation: {}.", lwM2MClient.getEndpoint(), targetId); - sendRequest(lwM2MClient, request, timeout); + log.info("[{}] Send observation: {}.", client.getEndpoint(), targetId); + sendRequest(client, request, timeout); } } } - public void sendDiscoverRequest(LwM2mClient lwM2MClient, String targetId, Long timeout) { + @Override + public void sendExecuteRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback) { + sendExecuteRequest(client, targetId, null, timeout, callback); + } + + @Override + public void sendExecuteRequest(LwM2mClient client, String targetId, Object params, Long timeout, DownlinkRequestCallback callback) { + String target = fromVersionedIdToObjectId(targetId); + LwM2mPath resultIds = new LwM2mPath(target); + ResourceModel resourceModelExecute = client.getResourceModel(targetId, this.config.getModelProvider()); + if (resourceModelExecute != null) { + ExecuteRequest request; + if (params != null && !resourceModelExecute.multiple) { + request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModelExecute.type, ResourceModel.Type.STRING, resultIds)); + } else { + request = new ExecuteRequest(target); + } + sendRequest(client, request, timeout); + } + } + + public void sendDeleteRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback) { + String target = fromVersionedIdToObjectId(targetId); + sendRequest(client, new DeleteRequest(target), timeout); + } + + @Override + public void sendCancelObserveRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback) { + int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), fromVersionedIdToObjectId(targetId)); + String observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LW2M_INFO, OBSERVE_CANCEL.name(), targetId, observeCancelCnt); + if (callback != null) { + callback.onSuccess(client, observeCancelMsg); + } + } + + @Override + public void sendCancelAllRequest(LwM2mClient client, Long timeout, DownlinkRequestCallback callback) { + int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration()); + String observeCancelMsg = String.format("%s: type operation %s paths: All count: %d", LOG_LW2M_INFO, OBSERVE_CANCEL.name(), observeCancelCnt); + if (callback != null) { + callback.onSuccess(client, observeCancelMsg); + } + } + + @Override + public void sendDiscoverRequest(LwM2mClient client, String targetId, Long timeout) { String objectId = fromVersionedIdToObjectId(targetId); - if (objectId != null && lwM2MClient.isValidObjectVersion(targetId)) { - sendRequest(lwM2MClient, new DiscoverRequest(objectId), timeout); + if (objectId != null && client.isValidObjectVersion(targetId)) { + sendRequest(client, new DiscoverRequest(objectId), timeout); } } - public void sendWriteAttributesRequest(LwM2mClient lwM2MClient, String targetId, ObjectAttributes params, Long timeout) { + @Override + public void sendWriteAttributesRequest(LwM2mClient client, String targetId, ObjectAttributes params, Long timeout) { String objectId = fromVersionedIdToObjectId(targetId); - if (objectId != null && lwM2MClient.isValidObjectVersion(targetId) && params != null) { + if (objectId != null && client.isValidObjectVersion(targetId) && params != null) { List attributes = new LinkedList<>(); // Dimension and Object version are read only attributes. // addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255); @@ -186,7 +234,55 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { addAttribute(attributes, LESSER_THAN, params.getLt()); addAttribute(attributes, STEP, params.getSt()); AttributeSet attributeSet = new AttributeSet(attributes); - sendRequest(lwM2MClient, new WriteAttributesRequest(objectId, attributeSet), timeout); + sendRequest(client, new WriteAttributesRequest(objectId, attributeSet), timeout); + } + } + + @Override + public void sendWriteReplaceRequest(LwM2mClient client, String targetIdVer, Object newValue, Long timeout, DownlinkRequestCallback callback) { + ResourceModel resourceModelWrite = client.getResourceModel(targetIdVer, this.config.getModelProvider()); + if (resourceModelWrite != null) { + ContentFormat contentFormat = convertResourceModelTypeToContentFormat(client, resourceModelWrite.type); + try { + LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(targetIdVer)); + WriteRequest request = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, + path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), newValue); + sendRequest(client, request, timeout); + } catch (Exception e) { + callback.onError(client, e.getMessage(), e); + } + } + } + + @Override + public void sendWriteUpdateRequest(LwM2mClient client, String targetIdVer, Object newValue, ContentFormat contentFormat, Long timeout, DownlinkRequestCallback callback) { + LwM2mPath resultIds = new LwM2mPath(fromVersionedIdToObjectId(targetIdVer)); + if (resultIds.isResource()) { + /* + * send request: path = '/3/0' node == wM2mObjectInstance + * with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} + **/ + Collection resources = client.getNewResourceForInstance(targetIdVer, newValue, this.config.getModelProvider(), this.converter); + ResourceModel resourceModelWrite = client.getResourceModel(targetIdVer, this.config.getModelProvider()); + WriteRequest request = new WriteRequest(WriteRequest.Mode.UPDATE, convertResourceModelTypeToContentFormat(client, resourceModelWrite.type), resultIds.getObjectId(), + resultIds.getObjectInstanceId(), resources); + sendRequest(client, request, timeout); + } else if (resultIds.isObjectInstance()) { + /* + * params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" + * int rscId = resultIds.getObjectInstanceId(); + * contentFormat – Format of the payload (TLV or JSON). + */ + Collection resources = client.getNewResourcesForInstance(targetIdVer, newValue, this.config.getModelProvider(), this.converter); + if (resources.size() > 0) { + contentFormat = contentFormat.equals(ContentFormat.JSON) ? contentFormat : client.getDefaultContentFormat(); + WriteRequest request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); + sendRequest(client, request, timeout); + } else { + callback.onError(client, "No resources to update!", new IllegalArgumentException()); + } + } else { + callback.onError(client, "Update of the root level object is not supported yet", new IllegalArgumentException()); } } @@ -204,32 +300,49 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } } - public void sendAllRequest(LwM2mClient lwM2MClient, String targetIdVer, LwM2mTypeOper typeOper, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { - sendAllRequest(lwM2MClient, targetIdVer, typeOper, lwM2MClient.getDefaultContentFormat(), params, timeoutInMs, lwm2mClientRpcRequest); + private static ContentFormat convertResourceModelTypeToContentFormat(LwM2mClient client, ResourceModel.Type type) { + switch (type) { + case BOOLEAN: + case STRING: + case TIME: + case INTEGER: + case FLOAT: + return client.getDefaultContentFormat(); + case OPAQUE: + return ContentFormat.OPAQUE; + case OBJLNK: + return ContentFormat.LINK; + default: + } + throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type); + } + + public void sendAllRequest(LwM2mClient client, String targetIdVer, LwM2mTypeOper typeOper, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { + sendAllRequest(client, targetIdVer, typeOper, client.getDefaultContentFormat(), params, timeoutInMs, lwm2mClientRpcRequest); } - public void sendAllRequest(LwM2mClient lwM2MClient, String targetIdVer, LwM2mTypeOper typeOper, + public void sendAllRequest(LwM2mClient client, String targetIdVer, LwM2mTypeOper typeOper, ContentFormat contentFormat, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { - Registration registration = lwM2MClient.getRegistration(); + Registration registration = client.getRegistration(); try { String target = fromVersionedIdToObjectId(targetIdVer); if (contentFormat == null) { - contentFormat = ContentFormat.DEFAULT; + 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 (lwM2MClient.isValidObjectVersion(targetIdVer)) { + if (client.isValidObjectVersion(targetIdVer)) { timeoutInMs = timeoutInMs > 0 ? timeoutInMs : DEFAULT_TIMEOUT; - SimpleDownlinkRequest request = createRequest(registration, lwM2MClient, typeOper, contentFormat, target, + SimpleDownlinkRequest request = createRequest(registration, client, typeOper, contentFormat, target, targetIdVer, resultIds, params, lwm2mClientRpcRequest); if (request != null) { try { - this.sendRequest(lwM2MClient, request, timeoutInMs, lwm2mClientRpcRequest); + this.sendRequest(client, request, timeoutInMs, lwm2mClientRpcRequest); } catch (ClientSleepingException e) { SimpleDownlinkRequest finalRequest = request; long finalTimeoutInMs = timeoutInMs; LwM2mClientRpcRequest finalRpcRequest = lwm2mClientRpcRequest; - lwM2MClient.getQueuedRequests().add(() -> sendRequest(lwM2MClient, finalRequest, finalTimeoutInMs, finalRpcRequest)); + client.getQueuedRequests().add(() -> sendRequest(client, finalRequest, finalTimeoutInMs, finalRpcRequest)); } catch (Exception e) { log.error("[{}] [{}] [{}] Failed to send downlink.", registration.getEndpoint(), targetIdVer, typeOper.name(), e); } @@ -246,7 +359,7 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } else if (!OBSERVE_CANCEL.name().equals(typeOper.name())) { log.error("[{}], [{}] - [{}] error SendRequest", registration.getEndpoint(), typeOper.name(), targetIdVer); if (lwm2mClientRpcRequest != null) { - ResourceModel resourceModel = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); + 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); } @@ -270,7 +383,7 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } String msg = String.format("%s: type operation %s paths - %s", LOG_LW2M_INFO, typeOper.name(), paths); - this.handler.sendLogsToThingsboard(lwM2MClient, msg); + this.handler.sendLogsToThingsboard(client, msg); if (lwm2mClientRpcRequest != null) { String valueMsg = String.format("Paths - %s", paths); handler.sentRpcResponse(lwm2mClientRpcRequest, CONTENT.name(), valueMsg, LOG_LW2M_VALUE); @@ -289,18 +402,18 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { observeCancelMsg = String.format("%s: type operation %s paths: All count: %d", LOG_LW2M_INFO, OBSERVE_CANCEL.name(), observeCancelCnt); } - this.afterObserveCancel(lwM2MClient, observeCancelCnt, observeCancelMsg, lwm2mClientRpcRequest); + this.afterObserveCancel(client, observeCancelCnt, observeCancelMsg, lwm2mClientRpcRequest); break; // lwm2mClientRpcRequest != null case FW_UPDATE: - handler.getInfoFirmwareUpdate(lwM2MClient, lwm2mClientRpcRequest); + 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(lwM2MClient, msg); + 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); @@ -308,7 +421,7 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } } - private SimpleDownlinkRequest createRequest(Registration registration, LwM2mClient lwM2MClient, LwM2mTypeOper typeOper, + private SimpleDownlinkRequest createRequest(Registration registration, LwM2mClient client, LwM2mTypeOper typeOper, ContentFormat contentFormat, String target, String targetIdVer, LwM2mPath resultIds, Object params, LwM2mClientRpcRequest rpcRequest) { SimpleDownlinkRequest request = null; @@ -337,7 +450,7 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } break; case EXECUTE: - ResourceModel resourceModelExecute = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); + ResourceModel resourceModelExecute = client.getResourceModel(targetIdVer, this.config.getModelProvider()); if (resourceModelExecute != null) { if (params != null && !resourceModelExecute.multiple) { request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModelExecute.type, ResourceModel.Type.STRING, resultIds)); @@ -353,12 +466,12 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { * contentFormat -> TLV, TLV, TLV, TLV, OPAQUE, TLV, LINK * JSON, TEXT; **/ - ResourceModel resourceModelWrite = lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()); + ResourceModel resourceModelWrite = client.getResourceModel(targetIdVer, this.config.getModelProvider()); if (resourceModelWrite != null) { - contentFormat = getContentFormatByResourceModelType(resourceModelWrite, contentFormat); - request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), - resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModelWrite.type, - lwM2MClient, rpcRequest); +// contentFormat = getContentFormatByResourceModelType(resourceModelWrite, contentFormat); +// request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), +// resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModelWrite.type, +// client, rpcRequest); } break; case WRITE_UPDATE: @@ -367,14 +480,14 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { * send request: path = '/3/0' node == wM2mObjectInstance * with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} **/ - Collection resources = lwM2MClient.getNewResourceForInstance( + Collection resources = client.getNewResourceForInstance( targetIdVer, params, this.config.getModelProvider(), this.converter); - contentFormat = getContentFormatByResourceModelType(lwM2MClient.getResourceModel(targetIdVer, this.config.getModelProvider()), - contentFormat); - request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), - resultIds.getObjectInstanceId(), resources); +// contentFormat = getContentFormatByResourceModelType(client.getResourceModel(targetIdVer, this.config.getModelProvider()), +// contentFormat); +// request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), +// resultIds.getObjectInstanceId(), resources); } /** * params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" @@ -383,7 +496,7 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { */ else if (resultIds.isObjectInstance()) { if (((ConcurrentHashMap) params).size() > 0) { - Collection resources = lwM2MClient.getNewResourcesForInstance( + Collection resources = client.getNewResourcesForInstance( targetIdVer, params, this.config.getModelProvider(), this.converter); @@ -407,33 +520,33 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { return request; } - private void sendRequest(LwM2mClient lwM2MClient, SimpleDownlinkRequest request, long timeoutInMs) { + private void sendRequest(LwM2mClient client, SimpleDownlinkRequest request, long timeoutInMs) { try { - sendRequest(lwM2MClient, request, timeoutInMs, null); + sendRequest(client, request, timeoutInMs, null); } catch (ClientSleepingException e) { //TODO: this may cause infinite loop / memory leak. - lwM2MClient.getQueuedRequests().add(() -> sendRequest(lwM2MClient, request, timeoutInMs, null)); + client.getQueuedRequests().add(() -> sendRequest(client, request, timeoutInMs, null)); } } @SuppressWarnings({"error sendRequest"}) - private void sendRequest(LwM2mClient lwM2MClient, SimpleDownlinkRequest request, long timeoutInMs, LwM2mClientRpcRequest rpcRequest) { - Registration registration = lwM2MClient.getRegistration(); + private void sendRequest(LwM2mClient client, SimpleDownlinkRequest request, long timeoutInMs, LwM2mClientRpcRequest rpcRequest) { + Registration registration = client.getRegistration(); context.getServer().send(registration, request, timeoutInMs, (ResponseCallback) response -> { - if (!lwM2MClient.isInit()) { - lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); + if (!client.isInit()) { + client.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } if (CoAP.ResponseCode.isSuccess(((Response) response.getCoapResponse()).getCode())) { - this.handleResponse(lwM2MClient, request.getPath().toString(), response, request, rpcRequest); + 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(lwM2MClient, msg); + handler.sendLogsToThingsboard(client, msg); 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.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); + if (!client.isInit()) { + client.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); } /** Not Found */ if (rpcRequest != null) { @@ -442,11 +555,11 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { /** Not Found set setClient_fw_info... = empty **/ - if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { - lwM2MClient.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); + if (client.getFwUpdate() != null && client.getFwUpdate().isInfoFwSwUpdate()) { + client.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); } - if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { - lwM2MClient.getSwUpdate().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()); @@ -459,11 +572,11 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { /** version == null set setClient_fw_info... = empty **/ - if (lwM2MClient.getFwUpdate() != null && lwM2MClient.getFwUpdate().isInfoFwSwUpdate()) { - lwM2MClient.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); + if (client.getFwUpdate() != null && client.getFwUpdate().isInfoFwSwUpdate()) { + client.getFwUpdate().initReadValue(handler, this, request.getPath().toString()); } - if (lwM2MClient.getSwUpdate() != null && lwM2MClient.getSwUpdate().isInfoFwSwUpdate()) { - lwM2MClient.getSwUpdate().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()); @@ -471,12 +584,12 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { if (request.getPath().toString().equals(FW_UPDATE_ID) || request.getPath().toString().equals(SW_INSTALL_ID)) { this.afterExecuteFwSwUpdateError(registration, request, e.getMessage()); } - if (!lwM2MClient.isInit()) { - lwM2MClient.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); + 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(lwM2MClient, msg); + 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); @@ -484,51 +597,51 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { }); } - private WriteRequest getWriteRequestSingleResource(ContentFormat contentFormat, Integer objectId, Integer instanceId, - Integer resourceId, Object value, ResourceModel.Type type, - LwM2mClient client, LwM2mClientRpcRequest rpcRequest) { - try { - 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 - 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: + private WriteRequest getWriteRequestSingleResource(ResourceModel.Type type, ContentFormat contentFormat, int objectId, int instanceId, int resourceId, Object value) { + switch (type) { + case STRING: // String + return new WriteRequest(contentFormat, objectId, instanceId, resourceId, value.toString()); + case INTEGER: // Long + final long valueInt = Integer.toUnsignedLong(Integer.parseInt(value.toString())); + return new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueInt); + case OBJLNK: // ObjectLink + return new WriteRequest(contentFormat, objectId, instanceId, resourceId, ObjectLink.fromPath(value.toString())); + case BOOLEAN: // Boolean + return new WriteRequest(contentFormat, objectId, instanceId, resourceId, Boolean.parseBoolean(value.toString())); + case FLOAT: // Double + return new WriteRequest(contentFormat, objectId, instanceId, resourceId, Double.parseDouble(value.toString())); + case TIME: // Date + Date date = new Date(Long.decode(value.toString())); + return new WriteRequest(contentFormat, objectId, instanceId, resourceId, date); + case OPAQUE: // byte[] value, base64 + byte[] valueRequest; + if (value instanceof byte[]) { + valueRequest = (byte[]) value; + } else { + valueRequest = Hex.decodeHex(value.toString().toCharArray()); } - } - 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) { - 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()); - handler.sendLogsToThingsboard(client, msg); - 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); - handler.sentRpcResponse(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); - } - return null; + return new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueRequest); + default: } +// TODO: throw exception and execute callback. +//// 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) { +// 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()); +// handler.sendLogsToThingsboard(client, msg); +// 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); +// handler.sentRpcResponse(rpcRequest, BAD_REQUEST.getName(), errorMsg, LOG_LW2M_ERROR); +// } +// return null; +// } } private void handleResponse(LwM2mClient lwM2mClient, final String path, LwM2mResponse response, @@ -597,7 +710,8 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } } - private void infoWriteResponse(LwM2mClient lwM2mClient, LwM2mResponse response, SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { + private void infoWriteResponse(LwM2mClient lwM2mClient, LwM2mResponse response, SimpleDownlinkRequest + request, LwM2mClientRpcRequest rpcRequest) { try { Registration registration = lwM2mClient.getRegistration(); LwM2mNode node = ((WriteRequest) request).getNode(); @@ -655,43 +769,46 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { * send operation Execute */ private void afterWriteSuccessFwSwUpdate(Registration registration, SimpleDownlinkRequest request) { - LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); - if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && lwM2MClient.getFwUpdate() != null) { - lwM2MClient.getFwUpdate().setStateUpdate(DOWNLOADED.name()); - lwM2MClient.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); + LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); + if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && client.getFwUpdate() != null) { + client.getFwUpdate().setStateUpdate(DOWNLOADED.name()); + client.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); } - if (request.getPath().toString().equals(SW_PACKAGE_ID) && lwM2MClient.getSwUpdate() != null) { - lwM2MClient.getSwUpdate().setStateUpdate(DOWNLOADED.name()); - lwM2MClient.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); + if (request.getPath().toString().equals(SW_PACKAGE_ID) && client.getSwUpdate() != null) { + client.getSwUpdate().setStateUpdate(DOWNLOADED.name()); + client.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); } } /** * After finish operation FwSwUpdate Write (error): fw_state = FAILED */ - private void afterWriteFwSWUpdateError(Registration registration, SimpleDownlinkRequest request, String msgError) { - LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); - if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && lwM2MClient.getFwUpdate() != null) { - lwM2MClient.getFwUpdate().setStateUpdate(FAILED.name()); - lwM2MClient.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); + private void afterWriteFwSWUpdateError(Registration registration, SimpleDownlinkRequest request, String + msgError) { + LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); + if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && client.getFwUpdate() != null) { + client.getFwUpdate().setStateUpdate(FAILED.name()); + client.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); } - if (request.getPath().toString().equals(SW_PACKAGE_ID) && lwM2MClient.getSwUpdate() != null) { - lwM2MClient.getSwUpdate().setStateUpdate(FAILED.name()); - lwM2MClient.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); + if (request.getPath().toString().equals(SW_PACKAGE_ID) && client.getSwUpdate() != null) { + client.getSwUpdate().setStateUpdate(FAILED.name()); + client.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); } } - private void afterExecuteFwSwUpdateError(Registration registration, SimpleDownlinkRequest request, String msgError) { - LwM2mClient lwM2MClient = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); - if (request.getPath().toString().equals(FW_UPDATE_ID) && lwM2MClient.getFwUpdate() != null) { - lwM2MClient.getFwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); + private void afterExecuteFwSwUpdateError(Registration registration, SimpleDownlinkRequest request, String + msgError) { + LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); + if (request.getPath().toString().equals(FW_UPDATE_ID) && client.getFwUpdate() != null) { + client.getFwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); } - if (request.getPath().toString().equals(SW_INSTALL_ID) && lwM2MClient.getSwUpdate() != null) { - lwM2MClient.getSwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); + if (request.getPath().toString().equals(SW_INSTALL_ID) && client.getSwUpdate() != null) { + client.getSwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); } } - private void afterObserveCancel(LwM2mClient lwM2mClient, int observeCancelCnt, String observeCancelMsg, LwM2mClientRpcRequest rpcRequest) { + private void afterObserveCancel(LwM2mClient lwM2mClient, int observeCancelCnt, String + observeCancelMsg, LwM2mClientRpcRequest rpcRequest) { handler.sendLogsToThingsboard(lwM2mClient, observeCancelMsg); log.warn("[{}]", observeCancelMsg); if (rpcRequest != null) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java index 0beb8ea7cb..40ad57e026 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java @@ -1,4 +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; +import org.eclipse.leshan.core.request.ContentFormat; +import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCallback; + public interface LwM2mDownlinkMsgHandler { + + void sendReadRequest(LwM2mClient client, String targetId, Long timeout); + + void sendReadRequest(LwM2mClient client, String targetId, ContentFormat contentFormat, Long timeout); + + void sendObserveRequest(LwM2mClient client, String targetId, Long timeout); + + void sendObserveRequest(LwM2mClient client, String targetId, ContentFormat contentFormat, Long timeout); + + void sendExecuteRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback); + + void sendExecuteRequest(LwM2mClient client, String targetId, Object params, Long timeout, DownlinkRequestCallback callback); + + void sendCancelObserveRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback); + + void sendCancelAllRequest(LwM2mClient client, Long timeout, DownlinkRequestCallback callback); + + void sendDiscoverRequest(LwM2mClient client, String targetId, Long timeout); + + void sendWriteAttributesRequest(LwM2mClient client, String targetId, ObjectAttributes params, Long timeout); + + void sendWriteReplaceRequest(LwM2mClient client, String targetIdVer, Object newValue, Long timeout, DownlinkRequestCallback callback); + + void sendWriteUpdateRequest(LwM2mClient client, String targetIdVer, Object newValue, ContentFormat contentFormat, Long timeout, DownlinkRequestCallback callback); } 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 a52fc0158d..9283fa22f5 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 @@ -48,6 +48,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -210,28 +211,6 @@ public class LwM2mTransportServerHelper { throw new CodecException("Invalid ResourceModel_Type for resource %s, got %s", resourcePath, currentType); } - public static ContentFormat convertResourceModelTypeToContentFormat(ResourceModel.Type type) { - switch (type) { - case BOOLEAN: - case STRING: - case TIME: - case INTEGER: - case FLOAT: - return ContentFormat.TLV; - case OPAQUE: - return ContentFormat.OPAQUE; - case OBJLNK: - return ContentFormat.LINK; - default: - } - throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type); - } - - public static ContentFormat getContentFormatByResourceModelType(ResourceModel resourceModel, ContentFormat contentFormat) { - return contentFormat.equals(ContentFormat.TLV) ? convertResourceModelTypeToContentFormat(resourceModel.type) : - contentFormat; - } - public static Object getValueFromKvProto(TransportProtos.KeyValueProto kv) { switch (kv.getType()) { case BOOLEAN_V: 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 a6586da0cc..5c210d2af3 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 @@ -302,7 +302,7 @@ public class LwM2mClient implements Cloneable { Map resourceModels = modelProvider.getObjectModel(registration) .getObjectModel(pathIds.getObjectId()).resources; resourceModels.forEach((resId, resourceModel) -> { - if (resId == pathIds.getResourceId()) { + if (resId.equals(pathIds.getResourceId())) { resources.add(LwM2mSingleResource.newResource(resId, converter.convertValue(params, equalsResourceTypeGetSimpleName(params), resourceModel.type, pathIds), resourceModel.type)); @@ -318,8 +318,8 @@ public class LwM2mClient implements Cloneable { 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))); + if (((Map) params).containsKey(String.valueOf(resId))) { + Object value = ((Map) params).get((String.valueOf(resId))); resources.add(LwM2mSingleResource.newResource(resId, converter.convertValue(value, equalsResourceTypeGetSimpleName(value), resourceModel.type, pathIds), resourceModel.type)); 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 56a601b3d2..efc5aa9657 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 @@ -25,7 +25,7 @@ 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.DefaultLwM2MUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mDownlinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import java.util.ArrayList; @@ -156,7 +156,7 @@ public class LwM2mFwSwUpdate { } } - public void initReadValue(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request, String pathIdVer) { + public void initReadValue(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request, String pathIdVer) { if (pathIdVer != null) { this.pendingInfoRequestsStart.remove(pathIdVer); } @@ -176,7 +176,7 @@ public class LwM2mFwSwUpdate { * Send FsSw to Lwm2mClient: * before operation Write: fw_state = DOWNLOADING */ - public void writeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request) { + 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); @@ -189,8 +189,7 @@ public class LwM2mFwSwUpdate { int chunkSize = 0; int chunk = 0; byte[] firmwareChunk = handler.otaPackageDataCache.get(this.currentId.toString(), chunkSize, chunk); - request.sendAllRequest(this.lwM2MClient, targetIdVer, WRITE_REPLACE, ContentFormat.OPAQUE, - firmwareChunk, handler.config.getTimeout(), this.rpcRequest); + request.sendWriteReplaceRequest(this.lwM2MClient, targetIdVer, firmwareChunk, handler.config.getTimeout(), this.rpcRequest); } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) { Registration registration = this.getLwM2MClient().getRegistration(); // String api = handler.config.getHostRequests(); @@ -198,8 +197,7 @@ public class LwM2mFwSwUpdate { int port = registration.getIdentity().isSecure() ? handler.config.getSecurePort() : handler.config.getPort(); String uri = "coap://" + api + ":" + Integer.valueOf(port) + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + this.currentId.toString(); log.warn("89) coapUri: [{}]", uri); - request.sendAllRequest(this.lwM2MClient, targetIdVer, WRITE_REPLACE, null, - uri, handler.config.getTimeout(), this.rpcRequest); + request.sendWriteReplaceRequest(this.lwM2MClient, targetIdVer, uri, handler.config.getTimeout(), this.rpcRequest); } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) { } @@ -230,9 +228,9 @@ public class LwM2mFwSwUpdate { * fw_state/sw_state = UPDATING * send execute */ - public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request) { + public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) { this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); - request.sendAllRequest(this.lwM2MClient, this.pathInstallId, EXECUTE, null, 0, this.rpcRequest); + request.sendExecuteRequest(this.lwM2MClient, this.pathInstallId, handler.config.getTimeout(), this.rpcRequest); } /** @@ -381,14 +379,14 @@ public class LwM2mFwSwUpdate { return LwM2mTransportUtil.UpdateResultSw.NOT_ENOUGH_STORAGE.code <= updateResult; } - private void observeStateUpdate(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler 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); - } +// 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) { @@ -401,7 +399,7 @@ public class LwM2mFwSwUpdate { } } - public void sendReadObserveInfo(DefaultLwM2mDownlinkMsgHandler request) { + public void sendReadObserveInfo(LwM2mDownlinkMsgHandler request) { this.infoFwSwUpdate = true; this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( this.pathStateId, this.lwM2MClient.getRegistration())); @@ -418,7 +416,7 @@ public class LwM2mFwSwUpdate { this.pathNameId, this.lwM2MClient.getRegistration())); } this.pendingInfoRequestsStart.forEach(pathIdVer -> { - request.sendAllRequest(this.lwM2MClient, pathIdVer, OBSERVE, null, 0, this.rpcRequest); + request.sendObserveRequest(this.lwM2MClient, pathIdVer, 0, this.rpcRequest); }); } @@ -433,7 +431,7 @@ public class LwM2mFwSwUpdate { * - after success finished operation Execute (FwUpdate) Update Result == 1 ("Firmware updated successfully") * - finished operation Execute (FwUpdate) */ - public void updateStateOta(DefaultLwM2MUplinkMsgHandler handler, DefaultLwM2mDownlinkMsgHandler request, + 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))) { 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 new file mode 100644 index 0000000000..84f1854e1d --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DownlinkRequestCallback.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.transport.lwm2m.server.downlink; + +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public interface DownlinkRequestCallback { + + void onSuccess(LwM2mClient client, String msg); + + void onError(LwM2mClient client, String msg, Exception e); +} 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 9a36b79051..12a256ebed 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 @@ -117,7 +117,7 @@ public class LwM2mValueConverterImpl implements LwM2mValueConverter { switch (currentType) { case INTEGER: log.debug("Trying to convert long value {} to date", value); - /** let's assume we received the millisecond since 1970/1/1 */ + /* let's assume we received the millisecond since 1970/1/1 */ return new Date(((Number) value).longValue() * 1000L); case STRING: log.debug("Trying to convert string value {} to date", value); From 489c67778a4009964b0686c834d22ba591e2a588 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 11 Jun 2021 11:10:06 +0300 Subject: [PATCH 039/231] implemented persisted RPC --- .../main/data/upgrade/3.2.2/schema_update.sql | 12 ++ .../server/actors/ActorSystemContext.java | 23 +++- .../server/actors/device/DeviceActor.java | 2 +- .../device/DeviceActorMessageProcessor.java | 109 +++++++++++++++--- .../server/controller/BaseController.java | 18 +++ .../server/controller/RpcController.java | 19 ++- .../action/RuleEngineEntityActionService.java | 2 +- .../rpc/DefaultTbCoreDeviceRpcService.java | 1 + .../rpc/DefaultTbRuleEngineRpcService.java | 2 +- .../server/service/rpc/TbRpcService.java | 69 +++++++++++ .../service/security/AccessValidator.java | 25 ++++ .../permission/CustomerUserPermissions.java | 19 +++ .../service/security/permission/Resource.java | 3 +- .../permission/TenantAdminPermissions.java | 1 + .../server/dao/rpc/RpcService.java | 37 ++++++ .../server/common/data/EntityType.java | 2 +- .../common/data/id/EntityIdFactory.java | 2 + .../server/common/data/id/RpcId.java | 39 +++++++ .../server/common/data/rpc/Rpc.java | 54 +++++++++ .../server/common/data/rpc/RpcStatus.java | 20 ++++ .../common/msg/rpc/ToDeviceRpcRequest.java | 1 + common/queue/src/main/proto/queue.proto | 10 ++ .../transport/coap/CoapTransportResource.java | 35 +++++- .../transport/http/DeviceApiController.java | 40 +++++-- .../secure/LwM2MBootstrapSecurityStore.java | 2 +- .../DefaultLwM2MTransportMsgHandler.java | 4 +- .../lwm2m/server/LwM2mSessionMsgListener.java | 34 ++++-- .../transport/mqtt/MqttTransportHandler.java | 30 ++++- .../mqtt/session/GatewayDeviceSessionCtx.java | 36 +++++- .../mqtt/session/GatewaySessionHandler.java | 7 +- .../snmp/session/DeviceSessionContext.java | 17 +++ .../common/transport/TransportService.java | 3 + .../service/DefaultTransportService.java | 9 ++ .../server/dao/model/ModelConstants.java | 11 ++ .../server/dao/model/sql/RpcEntity.java | 102 ++++++++++++++++ .../server/dao/rpc/BaseRpcService.java | 60 ++++++++++ .../thingsboard/server/dao/rpc/RpcDao.java | 27 +++++ .../server/dao/sql/rpc/JpaRpcDao.java | 56 +++++++++ .../server/dao/sql/rpc/RpcRepository.java | 28 +++++ .../resources/sql/schema-entities-hsql.sql | 11 ++ .../main/resources/sql/schema-entities.sql | 11 ++ .../resources/sql/hsql/drop-all-tables.sql | 1 + .../resources/sql/psql/drop-all-tables.sql | 1 + .../api/RuleEngineDeviceRpcRequest.java | 1 + .../rule/engine/rpc/TbSendRPCRequestNode.java | 4 + 45 files changed, 940 insertions(+), 60 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.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 5647c301cf..dec3472970 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 @@ -135,3 +135,15 @@ $$; ALTER TABLE api_usage_state ADD COLUMN IF NOT EXISTS alarm_exec VARCHAR(32); UPDATE api_usage_state SET alarm_exec = 'ENABLED' WHERE alarm_exec IS NULL; + +CREATE TABLE IF NOT EXISTS rpc ( + id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid NOT NULL, + device_id uuid NOT NULL, + expiration_time bigint NOT NULL, + request varchar(10000000) NOT NULL, + response varchar(10000000), + status varchar(255) NOT NULL +); + diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index b52f85af92..5f97f3151f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -65,6 +65,7 @@ import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.resource.ResourceService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleNodeStateService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -80,9 +81,9 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; +import org.thingsboard.server.service.rpc.TbRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; import org.thingsboard.server.service.script.JsExecutorService; import org.thingsboard.server.service.script.JsInvokeService; @@ -303,23 +304,33 @@ public class ActorSystemContext { @Lazy @Autowired(required = false) - @Getter private EdgeService edgeService; + @Getter + private EdgeService edgeService; @Lazy @Autowired(required = false) - @Getter private EdgeEventService edgeEventService; + @Getter + private EdgeEventService edgeEventService; @Lazy @Autowired(required = false) - @Getter private EdgeRpcService edgeRpcService; + @Getter + private EdgeRpcService edgeRpcService; @Lazy @Autowired(required = false) - @Getter private ResourceService resourceService; + @Getter + private ResourceService resourceService; @Lazy @Autowired(required = false) - @Getter private OtaPackageService otaPackageService; + @Getter + private OtaPackageService otaPackageService; + + @Lazy + @Autowired(required = false) + @Getter + private TbRpcService tbRpcService; @Value("${actors.session.max_concurrent_sessions_per_device:1}") @Getter diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java index 6e2761c9c5..3e06b8387c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActor.java @@ -46,7 +46,7 @@ public class DeviceActor extends ContextAwareActor { super.init(ctx); log.debug("[{}][{}] Starting device actor.", processor.tenantId, processor.deviceId); try { - processor.initSessionTimeout(ctx); + processor.init(ctx); log.debug("[{}][{}] Device actor started.", processor.tenantId, processor.deviceId); } catch (Exception e) { log.warn("[{}][{}] Unknown failure", processor.tenantId, processor.deviceId, e); 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 5af11d87f5..0c36fad123 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 @@ -23,6 +23,7 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; @@ -38,12 +39,17 @@ import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.KvEntry; +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.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; @@ -52,8 +58,8 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.common.msg.timeout.DeviceActorServerSideRpcTimeoutMsg; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceSessionsCacheEntry; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; @@ -68,10 +74,12 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionType; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.ToDevicePersistedRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; @@ -162,19 +170,23 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { void processRpcRequest(TbActorCtx context, ToDeviceRpcRequestActorMsg msg) { ToDeviceRpcRequest request = msg.getMsg(); - ToDeviceRpcRequestBody body = request.getBody(); - ToDeviceRpcRequestMsg rpcRequest = ToDeviceRpcRequestMsg.newBuilder() - .setRequestId(rpcSeq++) - .setMethodName(body.getMethod()) - .setParams(body.getParams()) - .setExpirationTime(request.getExpirationTime()) - .setRequestIdMSB(request.getId().getMostSignificantBits()) - .setRequestIdLSB(request.getId().getLeastSignificantBits()) - .build(); + ToDeviceRpcRequestMsg rpcRequest = creteToDeviceRpcRequestMsg(request); long timeout = request.getExpirationTime() - System.currentTimeMillis(); + boolean persisted = request.isPersisted(); if (timeout <= 0) { log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime()); + + if (persisted) { + Rpc rpc = new Rpc(new RpcId(request.getId())); + rpc.setCreatedTime(System.currentTimeMillis()); + rpc.setTenantId(tenantId); + rpc.setDeviceId(deviceId); + rpc.setExpirationTime(request.getExpirationTime()); + rpc.setRequest(JacksonUtil.valueToTree(request)); + rpc.setStatus(RpcStatus.TIMEOUT); + systemContext.getTbRpcService().save(tenantId, rpc); + } return; } @@ -195,6 +207,22 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { syncSessionSet.forEach(rpcSubscriptions::remove); } + if (persisted) { + Rpc rpc = new Rpc(new RpcId(request.getId())); + rpc.setCreatedTime(System.currentTimeMillis()); + rpc.setTenantId(tenantId); + rpc.setDeviceId(deviceId); + rpc.setExpirationTime(request.getExpirationTime()); + rpc.setRequest(JacksonUtil.valueToTree(request)); + rpc.setStatus(sent ? RpcStatus.SENT : RpcStatus.QUEUED); + systemContext.getTbRpcService().save(tenantId, rpc); + if (!(sent || request.isOneway())) { + ObjectNode response = JacksonUtil.newObjectNode(); + response.put("rpcId", request.getId().toString()); + systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), JacksonUtil.toString(response), null)); + } + } + if (request.isOneway() && sent) { log.debug("[{}] Rpc command response sent [{}]!", deviceId, request.getId()); systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), null, null)); @@ -208,6 +236,20 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } } + private ToDeviceRpcRequestMsg creteToDeviceRpcRequestMsg(ToDeviceRpcRequest request) { + ToDeviceRpcRequestBody body = request.getBody(); + return ToDeviceRpcRequestMsg.newBuilder() + .setRequestId(rpcSeq++) + .setMethodName(body.getMethod()) + .setParams(body.getParams()) + .setExpirationTime(request.getExpirationTime()) + .setRequestIdMSB(request.getId().getMostSignificantBits()) + .setRequestIdLSB(request.getId().getLeastSignificantBits()) + .setOneway(request.isOneway()) + .setPersisted(request.isPersisted()) + .build(); + } + void processRpcResponsesFromEdge(TbActorCtx context, FromDeviceRpcResponseActorMsg responseMsg) { log.debug("[{}] Processing rpc command response from edge session", deviceId); ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(responseMsg.getRequestId()); @@ -229,6 +271,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId()); if (requestMd != null) { log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); + systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.TIMEOUT, null); systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); } @@ -270,7 +313,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { .setExpirationTime(request.getExpirationTime()) .setRequestIdMSB(request.getId().getMostSignificantBits()) .setRequestIdLSB(request.getId().getLeastSignificantBits()) + .setOneway(request.isOneway()) + .setPersisted(request.isPersisted()) .build(); + + if (request.isPersisted()) { + systemContext.getTbRpcService().save(tenantId, new RpcId(request.getId()), RpcStatus.SENT, null); + } sendToTransport(rpcRequest, sessionId, nodeId); }; } @@ -299,10 +348,13 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { if (msg.hasClaimDevice()) { handleClaimDeviceMsg(context, msg.getSessionInfo(), msg.getClaimDevice()); } + if (msg.hasPersistedRpcResponseMsg()) { + processPersistedRpcResponses(context, msg.getSessionInfo(), msg.getPersistedRpcResponseMsg()); + } callback.onSuccess(); } - private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg) { + private void handleClaimDeviceMsg(TbActorCtx context, SessionInfoProto sessionInfo, ClaimDeviceMsg msg) { DeviceId deviceId = new DeviceId(new UUID(msg.getDeviceIdMSB(), msg.getDeviceIdLSB())); systemContext.getClaimDevicesService().registerClaimingInfo(tenantId, deviceId, msg.getSecretKey(), msg.getDurationMs()); } @@ -441,11 +493,22 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { if (success) { systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), responseMsg.getPayload(), null)); + if (requestMd.getMsg().getMsg().isPersisted()) { + systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.SUCCESSFUL, JacksonUtil.toJsonNode(responseMsg.getPayload())); + } } else { log.debug("[{}] Rpc command response [{}] is stale!", deviceId, responseMsg.getRequestId()); + if (requestMd.getMsg().getMsg().isPersisted()) { + systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.FAILED, JacksonUtil.toJsonNode(responseMsg.getPayload())); + } } } + private void processPersistedRpcResponses(TbActorCtx context, SessionInfoProto sessionInfo, ToDevicePersistedRpcResponseMsg responseMsg) { + UUID rpcId = new UUID(responseMsg.getRequestIdMSB(), responseMsg.getRequestIdLSB()); + systemContext.getTbRpcService().save(tenantId, new RpcId(rpcId), RpcStatus.valueOf(responseMsg.getStatus()), null); + } + private void processSubscriptionCommands(TbActorCtx context, SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg subscribeCmd) { UUID sessionId = getSessionId(sessionInfo); if (subscribeCmd.getUnsubscribe()) { @@ -564,7 +627,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { void notifyTransportAboutProfileUpdate(UUID sessionId, SessionInfoMetaData sessionMd, DeviceCredentials deviceCredentials) { log.info("2) LwM2Mtype: "); - TransportProtos.ToTransportUpdateCredentialsProto.Builder notification = TransportProtos.ToTransportUpdateCredentialsProto.newBuilder(); + ToTransportUpdateCredentialsProto.Builder notification = ToTransportUpdateCredentialsProto.newBuilder(); notification.addCredentialsId(deviceCredentials.getCredentialsId()); notification.addCredentialsValue(deviceCredentials.getCredentialsValue()); ToTransportMsg msg = ToTransportMsg.newBuilder() @@ -639,7 +702,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { ListenableFuture future = systemContext.getEdgeEventService().saveAsync(edgeEvent); Futures.addCallback(future, new FutureCallback() { @Override - public void onSuccess( EdgeEvent result) { + public void onSuccess(EdgeEvent result) { systemContext.getClusterService().onEdgeEventUpdate(tenantId, edgeId); } @@ -755,8 +818,26 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { .addAllSessions(sessionsList).build().toByteArray()); } - void initSessionTimeout(TbActorCtx ctx) { + void init(TbActorCtx ctx) { schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout()); + PageLink pageLink = new PageLink(10); + PageData pageData; + do { + pageData = systemContext.getTbRpcService().findAllByDeviceIdAndStatus(deviceId, RpcStatus.QUEUED, pageLink); + pageData.getData().forEach(rpc -> { + ToDeviceRpcRequest msg = JacksonUtil.convertValue(rpc.getRequest(), ToDeviceRpcRequest.class); + long timeout = rpc.getExpirationTime() - System.currentTimeMillis(); + if (timeout <= 0) { + rpc.setStatus(RpcStatus.TIMEOUT); + systemContext.getTbRpcService().save(tenantId, rpc); + } else { + registerPendingRpcRequest(ctx, new ToDeviceRpcRequestActorMsg(systemContext.getServiceId(), msg), false, creteToDeviceRpcRequestMsg(msg), timeout); + } + }); + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } while (pageData.hasNext()); } void checkSessionsTimeout() { 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 7d41deb2ab..a731e806c1 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -69,6 +69,7 @@ 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.OtaPackageId; +import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TbResourceId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; @@ -83,6 +84,7 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.plugin.ComponentDescriptor; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.rule.RuleNode; @@ -106,6 +108,7 @@ 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.rpc.RpcService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; @@ -245,6 +248,9 @@ public abstract class BaseController { @Autowired protected OtaPackageStateService otaPackageStateService; + @Autowired + protected RpcService rpcService; + @Autowired protected TbQueueProducerProvider producerProvider; @@ -786,6 +792,18 @@ public abstract class BaseController { } } + Rpc checkRpcId(RpcId rpcId, Operation operation) throws ThingsboardException { + try { + validateId(rpcId, "Incorrect rpcId " + rpcId); + Rpc rpc = rpcService.findById(getCurrentUser().getTenantId(), rpcId); + checkNotNull(rpc); + accessControlService.checkPermission(getCurrentUser(), Resource.RPC, operation, rpcId, rpc); + return rpc; + } catch (Exception e) { + throw handleException(e, false); + } + } + @SuppressWarnings("unchecked") protected I emptyId(EntityType entityType) { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/RpcController.java index e71ffbc6d2..8a75aad485 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcController.java @@ -38,8 +38,10 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -93,6 +95,19 @@ public class RpcController extends BaseController { return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody); } + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/persisted/{rpcId}", method = RequestMethod.GET) + @ResponseBody + public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException { + checkParameter("RpcId", strRpc); + try { + RpcId rpcId = new RpcId(UUID.fromString(strRpc)); + return checkRpcId(rpcId, Operation.READ); + } catch (Exception e) { + throw handleException(e); + } + } + private DeferredResult handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException { try { JsonNode rpcRequestBody = jsonMapper.readTree(requestBody); @@ -103,6 +118,7 @@ public class RpcController extends BaseController { long timeout = rpcRequestBody.has("timeout") ? rpcRequestBody.get("timeout").asLong() : defaultTimeout; long expTime = System.currentTimeMillis() + Math.max(minTimeout, timeout); UUID rpcRequestUUID = rpcRequestBody.has("requestUUID") ? UUID.fromString(rpcRequestBody.get("requestUUID").asText()) : UUID.randomUUID(); + boolean persisted = rpcRequestBody.has("persisted") && rpcRequestBody.get("persisted").asBoolean(); accessValidator.validate(currentUser, Operation.RPC_CALL, deviceId, new HttpValidationCallback(response, new FutureCallback>() { @Override public void onSuccess(@Nullable DeferredResult result) { @@ -111,7 +127,8 @@ public class RpcController extends BaseController { deviceId, oneWay, expTime, - body + body, + persisted ); deviceRpcService.processRestApiRpcRequest(rpcRequest, fromDeviceRpcResponse -> reply(new LocalRequestMetaData(rpcRequest, currentUser, result), fromDeviceRpcResponse), currentUser); } diff --git a/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java index f1320d1a7a..754d32b704 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java @@ -54,7 +54,7 @@ public class RuleEngineEntityActionService { private static final ObjectMapper json = new ObjectMapper(); - public void pushEntityActionToRuleEngine(EntityId entityId, HasName entity, TenantId tenantId, CustomerId customerId, + public void pushEntityActionToRuleEngine(EntityId entityId, Object entity, TenantId tenantId, CustomerId customerId, ActionType actionType, User user, Object... additionalInfo) { String msgType = null; switch (actionType) { 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 85c26d4c4d..a69402201a 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 @@ -157,6 +157,7 @@ public class DefaultTbCoreDeviceRpcService implements TbCoreDeviceRpcService { metaData.putValue("originServiceId", serviceId); metaData.putValue("expirationTime", Long.toString(msg.getExpirationTime())); metaData.putValue("oneway", Boolean.toString(msg.isOneway())); + metaData.putValue("persisted", Boolean.toString(msg.isPersisted())); Device device = deviceService.findDeviceById(msg.getTenantId(), msg.getDeviceId()); if (device != null) { diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java index c3b038c35a..e16af3695b 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java @@ -100,7 +100,7 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi @Override public void sendRpcRequestToDevice(RuleEngineDeviceRpcRequest src, Consumer consumer) { ToDeviceRpcRequest request = new ToDeviceRpcRequest(src.getRequestUUID(), src.getTenantId(), src.getDeviceId(), - src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody())); + src.isOneway(), src.getExpirationTime(), new ToDeviceRpcRequestBody(src.getMethod(), src.getBody()), src.isPersisted()); forwardRpcRequestToDeviceActor(request, response -> { if (src.isRestApiCall()) { sendRpcResponseToTbCore(src.getOriginServiceId(), response); diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java new file mode 100644 index 0000000000..d85223c43a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java @@ -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. + */ +package org.thingsboard.server.service.rpc; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RpcId; +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.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.dao.rpc.RpcService; +import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.action.RuleEngineEntityActionService; + +@TbCoreComponent +@Service +@RequiredArgsConstructor +@Slf4j +public class TbRpcService { + private final RpcService rpcService; + private final RuleEngineEntityActionService ruleEngineEntityActionService; + + public void save(TenantId tenantId, Rpc rpc) { + Rpc saved = rpcService.save(tenantId, rpc); + ruleEngineEntityActionService.pushEntityActionToRuleEngine(saved.getId(), saved, tenantId, null, rpc.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); + } + + public void save(TenantId tenantId, RpcId rpcId, RpcStatus newStatus, JsonNode response) { + Rpc foundRpc = rpcService.findById(tenantId, rpcId); + if (foundRpc != null) { + foundRpc.setStatus(newStatus); + if (response != null) { + foundRpc.setResponse(response); + } + Rpc saved = rpcService.save(tenantId, foundRpc); + ruleEngineEntityActionService.pushEntityActionToRuleEngine(saved.getId(), saved, tenantId, null, ActionType.UPDATED, null); + } else { + log.warn("[{}] Failed to update RPC status because RPC was already deleted", rpcId); + } + } + + public Rpc findRpcById(TenantId tenantId, RpcId rpcId) { + return rpcService.findById(tenantId, rpcId); + } + + public PageData findAllByDeviceIdAndStatus(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { + return rpcService.findAllByDeviceIdAndStatus(deviceId, rpcStatus, pageLink); + } + +} 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 540a47e8b0..ab9d0de231 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 @@ -47,11 +47,13 @@ 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.OtaPackageId; +import org.thingsboard.server.common.data.id.RpcId; 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.rpc.Rpc; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.controller.HttpValidationCallback; @@ -65,6 +67,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.resource.ResourceService; +import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; @@ -137,6 +140,9 @@ public class AccessValidator { @Autowired protected OtaPackageService otaPackageService; + @Autowired + protected RpcService rpcService; + private ExecutorService executor; @PostConstruct @@ -235,6 +241,9 @@ public class AccessValidator { case OTA_PACKAGE: validateOtaPackage(currentUser, operation, entityId, callback); return; + case RPC: + validateRpc(currentUser, operation, entityId, callback); + return; default: //TODO: add support of other entities throw new IllegalStateException("Not Implemented!"); @@ -261,6 +270,22 @@ public class AccessValidator { } } + private void validateRpc(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { + ListenableFuture rpcFurure = rpcService.findRpcByIdAsync(currentUser.getTenantId(), new RpcId(entityId.getId())); + Futures.addCallback(rpcFurure, getCallback(callback, rpc -> { + if (rpc == null) { + return ValidationResult.entityNotFound("Rpc with requested id wasn't found!"); + } else { + try { + accessControlService.checkPermission(currentUser, Resource.RPC, operation, entityId, rpc); + } catch (ThingsboardException e) { + return ValidationResult.accessDenied(e.getMessage()); + } + return ValidationResult.ok(rpc); + } + }), executor); + } + private void validateDeviceProfile(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)); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java index fc1705d688..b0c39a72d2 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/CustomerUserPermissions.java @@ -41,6 +41,7 @@ public class CustomerUserPermissions extends AbstractPermissions { put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); put(Resource.WIDGET_TYPE, widgetsPermissionChecker); put(Resource.EDGE, customerEntityPermissionChecker); + put(Resource.RPC, rpcPermissionChecker); } private static final PermissionChecker customerEntityPermissionChecker = @@ -138,4 +139,22 @@ public class CustomerUserPermissions extends AbstractPermissions { } }; + + private static final PermissionChecker rpcPermissionChecker = new PermissionChecker.GenericPermissionChecker(Operation.READ) { + + @Override + @SuppressWarnings("unchecked") + public boolean hasPermission(SecurityUser user, Operation operation, EntityId entityId, HasTenantId entity) { + if (!super.hasPermission(user, operation, entityId, entity)) { + return false; + } + if (entity.getTenantId() == null || entity.getTenantId().isNullUid()) { + return true; + } + if (!user.getTenantId().equals(entity.getTenantId())) { + return false; + } + return true; + } + }; } 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 43c420a94a..c2890836b4 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 @@ -39,7 +39,8 @@ public enum Resource { API_USAGE_STATE(EntityType.API_USAGE_STATE), TB_RESOURCE(EntityType.TB_RESOURCE), OTA_PACKAGE(EntityType.OTA_PACKAGE), - EDGE(EntityType.EDGE); + EDGE(EntityType.EDGE), + RPC(EntityType.RPC); 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 8b4d44e938..ad753898e9 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 @@ -44,6 +44,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.TB_RESOURCE, tbResourcePermissionChecker); put(Resource.OTA_PACKAGE, tenantEntityPermissionChecker); put(Resource.EDGE, tenantEntityPermissionChecker); + put(Resource.RPC, tenantEntityPermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java new file mode 100644 index 0000000000..22446d143e --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.rpc; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RpcId; +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.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; + +public interface RpcService { + Rpc save(TenantId tenantId, Rpc rpc); + + void remove(TenantId tenantId, RpcId id); + + Rpc findById(TenantId tenantId, RpcId id); + + ListenableFuture findRpcByIdAsync(TenantId tenantId, RpcId id); + + PageData findAllByDeviceIdAndStatus(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink); +} 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 cf6c6fd9a7..224af1fc3b 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, OTA_PACKAGE, EDGE; + 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, OTA_PACKAGE, EDGE, RPC; } 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 a4b2327c75..6d8ddfdd18 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 @@ -75,6 +75,8 @@ public class EntityIdFactory { return new OtaPackageId(uuid); case EDGE: return new EdgeId(uuid); + case RPC: + return new RpcId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java new file mode 100644 index 0000000000..a4cd8797b0 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.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.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 final class RpcId extends UUIDBased implements EntityId { + + private static final long serialVersionUID = 1L; + + @JsonCreator + public RpcId(@JsonProperty("id") UUID id) { + super(id); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.RPC; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java new file mode 100644 index 0000000000..504c89b569 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/Rpc.java @@ -0,0 +1,54 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.rpc; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.BaseData; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RpcId; +import org.thingsboard.server.common.data.id.TenantId; + +@Data +@EqualsAndHashCode(callSuper = true) +public class Rpc extends BaseData implements HasTenantId { + private TenantId tenantId; + private DeviceId deviceId; + private long expirationTime; + private JsonNode request; + private JsonNode response; + private RpcStatus status; + + public Rpc() { + super(); + } + + public Rpc(RpcId id) { + super(id); + } + + public Rpc(Rpc rpc) { + super(rpc); + this.tenantId = rpc.getTenantId(); + this.deviceId = rpc.getDeviceId(); + this.expirationTime = rpc.getExpirationTime(); + this.request = rpc.getRequest(); + this.response = rpc.getResponse(); + this.status = rpc.getStatus(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java new file mode 100644 index 0000000000..fea9e30a75 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.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.rpc; + +public enum RpcStatus { + QUEUED, SENT, DELIVERED, SUCCESSFUL, TIMEOUT, FAILED +} diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java b/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java index 3cce7b1d33..b3b33146d0 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/rpc/ToDeviceRpcRequest.java @@ -34,5 +34,6 @@ public class ToDeviceRpcRequest implements Serializable { private final boolean oneway; private final long expirationTime; private final ToDeviceRpcRequestBody body; + private final boolean persisted; } diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index faf88c5620..03311a11f9 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -323,6 +323,8 @@ message ToDeviceRpcRequestMsg { int64 expirationTime = 4; int64 requestIdMSB = 5; int64 requestIdLSB = 6; + bool oneway = 7; + bool persisted = 8; } message ToDeviceRpcResponseMsg { @@ -330,6 +332,13 @@ message ToDeviceRpcResponseMsg { string payload = 2; } +message ToDevicePersistedRpcResponseMsg { + int32 requestId = 1; + int64 requestIdMSB = 2; + int64 requestIdLSB = 3; + string status = 4; +} + message ToServerRpcRequestMsg { int32 requestId = 1; string methodName = 2; @@ -433,6 +442,7 @@ message TransportToDeviceActorMsg { SubscriptionInfoProto subscriptionInfo = 7; ClaimDeviceMsg claimDevice = 8; ProvisionDeviceRequestMsg provisionDevice = 9; + ToDevicePersistedRpcResponseMsg persistedRpcResponseMsg = 10; } message TransportToRuleEngineMsg { 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 cd80aa42df..d8fe57446c 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 @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadCon 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.rpc.RpcStatus; import org.thingsboard.server.common.data.security.DeviceTokenCredentials; import org.thingsboard.server.common.msg.session.FeatureType; import org.thingsboard.server.common.msg.session.SessionMsgType; @@ -337,14 +338,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource { break; case TO_SERVER_RPC_REQUEST: transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, - transportConfigurationContainer.getRpcRequestDynamicMessageBuilder()), timeout); + transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), sessionInfo), timeout); transportService.process(sessionInfo, coapTransportAdaptor.convertToServerRpcRequest(sessionId, request), new CoapNoOpCallback(exchange)); break; case GET_ATTRIBUTES_REQUEST: transportService.registerSyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, - transportConfigurationContainer.getRpcRequestDynamicMessageBuilder()), timeout); + transportConfigurationContainer.getRpcRequestDynamicMessageBuilder(), sessionInfo), timeout); transportService.process(sessionInfo, coapTransportAdaptor.convertToGetAttributes(sessionId, request), new CoapNoOpCallback(exchange)); @@ -383,12 +384,12 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private void registerAsyncCoapSession(CoapExchange exchange, TransportProtos.SessionInfoProto sessionInfo, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, String token) { tokenToSessionInfoMap.putIfAbsent(token, sessionInfo); - transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder)); + transportService.registerAsyncSession(sessionInfo, getCoapSessionListener(exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder, sessionInfo)); transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.OPEN), null); } - private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) { - return new CoapSessionListener(this, exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder); + private CoapSessionListener getCoapSessionListener(CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, TransportProtos.SessionInfoProto sessionInfo) { + return new CoapSessionListener(this, exchange, coapTransportAdaptor, rpcRequestDynamicMessageBuilder, sessionInfo); } private String getTokenFromRequest(Request request) { @@ -510,12 +511,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource { private final CoapExchange exchange; private final CoapTransportAdaptor coapTransportAdaptor; private final DynamicMessage.Builder rpcRequestDynamicMessageBuilder; + private final TransportProtos.SessionInfoProto sessionInfo; - CoapSessionListener(CoapTransportResource coapTransportResource, CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder) { + CoapSessionListener(CoapTransportResource coapTransportResource, CoapExchange exchange, CoapTransportAdaptor coapTransportAdaptor, DynamicMessage.Builder rpcRequestDynamicMessageBuilder, TransportProtos.SessionInfoProto sessionInfo) { this.coapTransportResource = coapTransportResource; this.exchange = exchange; this.coapTransportAdaptor = coapTransportAdaptor; this.rpcRequestDynamicMessageBuilder = rpcRequestDynamicMessageBuilder; + this.sessionInfo = sessionInfo; } @Override @@ -558,11 +561,31 @@ public class CoapTransportResource extends AbstractCoapTransportResource { @Override public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg msg) { + boolean successful; try { exchange.respond(coapTransportAdaptor.convertToPublish(isConRequest(), msg, rpcRequestDynamicMessageBuilder)); + successful = true; } catch (AdaptorException e) { log.trace("Failed to reply due to error", e); exchange.respond(CoAP.ResponseCode.INTERNAL_SERVER_ERROR); + successful = false; + } + if (msg.getPersisted()) { + RpcStatus status; + if (successful) { + status = RpcStatus.FAILED; + } else if (msg.getOneway()) { + status = RpcStatus.SUCCESSFUL; + } else { + status = RpcStatus.DELIVERED; + } + TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder() + .setRequestId(msg.getRequestId()) + .setRequestIdLSB(msg.getRequestIdLSB()) + .setRequestIdMSB(msg.getRequestIdMSB()) + .setStatus(status.name()) + .build(); + coapTransportResource.transportService.process(sessionInfo, responseMsg, TransportServiceCallback.EMPTY); } } 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..cfb25e9d78 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.http; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; @@ -34,9 +35,10 @@ 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.ota.OtaPackageType; import org.thingsboard.server.common.data.TbTransportService; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportContext; import org.thingsboard.server.common.transport.TransportService; @@ -95,7 +97,9 @@ public class DeviceApiController implements TbTransportService { request.addAllSharedAttributeNames(sharedKeySet); } TransportService transportService = transportContext.getTransportService(); - transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), transportContext.getDefaultTimeout()); + transportService.registerSyncSession(sessionInfo, + new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo), + transportContext.getDefaultTimeout()); transportService.process(sessionInfo, request.build(), new SessionCloseOnErrorCallback(transportService, sessionInfo)); })); return responseWriter; @@ -151,7 +155,8 @@ public class DeviceApiController implements TbTransportService { transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { TransportService transportService = transportContext.getTransportService(); - transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), + transportService.registerSyncSession(sessionInfo, + new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo), timeout == 0 ? transportContext.getDefaultTimeout() : timeout); transportService.process(sessionInfo, SubscribeToRPCMsg.getDefaultInstance(), new SessionCloseOnErrorCallback(transportService, sessionInfo)); @@ -181,7 +186,9 @@ public class DeviceApiController implements TbTransportService { new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { JsonObject request = new JsonParser().parse(json).getAsJsonObject(); TransportService transportService = transportContext.getTransportService(); - transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), transportContext.getDefaultTimeout()); + transportService.registerSyncSession(sessionInfo, + new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo), + transportContext.getDefaultTimeout()); transportService.process(sessionInfo, ToServerRpcRequestMsg.newBuilder().setRequestId(0) .setMethodName(request.get("method").getAsString()) .setParams(request.get("params").toString()).build(), @@ -198,7 +205,8 @@ public class DeviceApiController implements TbTransportService { transportContext.getTransportService().process(DeviceTransportType.DEFAULT, ValidateDeviceTokenRequestMsg.newBuilder().setToken(deviceToken).build(), new DeviceAuthCallback(transportContext, responseWriter, sessionInfo -> { TransportService transportService = transportContext.getTransportService(); - transportService.registerSyncSession(sessionInfo, new HttpSessionListener(responseWriter), + transportService.registerSyncSession(sessionInfo, + new HttpSessionListener(responseWriter, transportContext.getTransportService(), sessionInfo), timeout == 0 ? transportContext.getDefaultTimeout() : timeout); transportService.process(sessionInfo, SubscribeToAttributeUpdatesMsg.getDefaultInstance(), new SessionCloseOnErrorCallback(transportService, sessionInfo)); @@ -372,13 +380,12 @@ public class DeviceApiController implements TbTransportService { } } + @RequiredArgsConstructor private static class HttpSessionListener implements SessionMsgListener { private final DeferredResult responseWriter; - - HttpSessionListener(DeferredResult responseWriter) { - this.responseWriter = responseWriter; - } + private final TransportService transportService; + private final SessionInfoProto sessionInfo; @Override public void onGetAttributesResponse(GetAttributeResponseMsg msg) { @@ -399,6 +406,21 @@ public class DeviceApiController implements TbTransportService { @Override public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg msg) { responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg, true).toString(), HttpStatus.OK)); + if (msg.getPersisted()) { + RpcStatus status; + if (msg.getOneway()) { + status = RpcStatus.SUCCESSFUL; + } else { + status = RpcStatus.DELIVERED; + } + TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder() + .setRequestId(msg.getRequestId()) + .setRequestIdLSB(msg.getRequestIdLSB()) + .setRequestIdMSB(msg.getRequestIdMSB()) + .setStatus(status.name()) + .build(); + transportService.process(sessionInfo, responseMsg, TransportServiceCallback.EMPTY); + } } @Override 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 ccc2e62117..1e7b272174 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 @@ -161,7 +161,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { LwM2MServerBootstrap profileLwm2mServer = mapper.readValue(bootstrapObject.get(LWM2M_SERVER).toString(), LwM2MServerBootstrap.class); UUID sessionUUiD = UUID.randomUUID(); TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); - context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, sessionInfo)); + context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, sessionInfo, context.getTransportService())); if (this.getValidatedSecurityMode(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap, lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer)) { lwM2MBootstrapConfig.bootstrapServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.bootstrapServer, profileServerBootstrap); lwM2MBootstrapConfig.lwm2mServer = new LwM2MServerBootstrap(lwM2MBootstrapConfig.lwm2mServer, profileLwm2mServer); 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 e0a85bc77d..b211e49d50 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 @@ -188,7 +188,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler if (lwM2MClient != null) { SessionInfoProto sessionInfo = this.getSessionInfoOrCloseSession(lwM2MClient); if (sessionInfo != null) { - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); + transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo, transportService)); TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder() .setSessionInfo(sessionInfo) .setSessionEvent(DefaultTransportService.getSessionEventMsg(SessionEvent.OPEN)) @@ -1320,7 +1320,7 @@ public class DefaultLwM2MTransportMsgHandler implements LwM2mTransportMsgHandler */ private void reportActivityAndRegister(SessionInfoProto sessionInfo) { if (sessionInfo != null && transportService.reportActivity(sessionInfo) == null) { - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); + transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo, transportService)); this.reportActivitySubscription(sessionInfo); } } 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 b71de7db1b..1636a1ae6c 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 @@ -17,12 +17,16 @@ package org.thingsboard.server.transport.lwm2m.server; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.ResourceType; +import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.transport.SessionMsgListener; +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.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponseMsg; @@ -35,14 +39,11 @@ import java.util.Optional; import java.util.UUID; @Slf4j +@RequiredArgsConstructor public class LwM2mSessionMsgListener implements GenericFutureListener>, SessionMsgListener { - private DefaultLwM2MTransportMsgHandler handler; - private TransportProtos.SessionInfoProto sessionInfo; - - public LwM2mSessionMsgListener(DefaultLwM2MTransportMsgHandler handler, TransportProtos.SessionInfoProto sessionInfo) { - this.handler = handler; - this.sessionInfo = sessionInfo; - } + private final DefaultLwM2MTransportMsgHandler handler; + private final TransportProtos.SessionInfoProto sessionInfo; + private final TransportService transportService; @Override public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { @@ -52,7 +53,7 @@ public class LwM2mSessionMsgListener implements GenericFutureListener { + ChannelFuture channelFuture = deviceSessionCtx.getChannel().writeAndFlush(payload); + if (rpcRequest.getPersisted()) { + channelFuture.addListener(future -> { + RpcStatus status; + Throwable t = future.cause(); + if (t != null) { + log.error("Failed delivering RPC command to device!", t); + status = RpcStatus.FAILED; + } else if (rpcRequest.getOneway()) { + status = RpcStatus.SUCCESSFUL; + } else { + status = RpcStatus.DELIVERED; + } + TransportProtos.ToDevicePersistedRpcResponseMsg msg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder() + .setRequestId(rpcRequest.getRequestId()) + .setRequestIdLSB(rpcRequest.getRequestIdLSB()) + .setRequestIdMSB(rpcRequest.getRequestIdMSB()) + .setStatus(status.name()) + .build(); + transportService.process(deviceSessionCtx.getSessionInfo(), msg, TransportServiceCallback.EMPTY); + }); + } + }); } catch (Exception e) { log.trace("[{}] Failed to convert device RPC command to MQTT msg", sessionId, e); } 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 707586b7de..9e3633cb4b 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 @@ -15,9 +15,13 @@ */ package org.thingsboard.server.transport.mqtt.session; +import io.netty.channel.ChannelFuture; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportService; +import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; @@ -32,9 +36,11 @@ import java.util.concurrent.ConcurrentMap; public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext implements SessionMsgListener { private final GatewaySessionHandler parent; + private final TransportService transportService; public GatewayDeviceSessionCtx(GatewaySessionHandler parent, TransportDeviceInfo deviceInfo, - DeviceProfile deviceProfile, ConcurrentMap mqttQoSMap) { + DeviceProfile deviceProfile, ConcurrentMap mqttQoSMap, + TransportService transportService) { super(UUID.randomUUID(), mqttQoSMap); this.parent = parent; setSessionInfo(SessionInfoProto.newBuilder() @@ -56,6 +62,7 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple .build()); setDeviceInfo(deviceInfo); setDeviceProfile(deviceProfile); + this.transportService = transportService; } @Override @@ -89,7 +96,32 @@ public class GatewayDeviceSessionCtx extends MqttDeviceAwareSessionContext imple @Override public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg request) { try { - parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent(parent::writeAndFlush); + parent.getPayloadAdaptor().convertToGatewayPublish(this, getDeviceInfo().getDeviceName(), request).ifPresent( + payload -> { + ChannelFuture channelFuture = parent.writeAndFlush(payload); + if (request.getPersisted()) { + channelFuture.addListener(future -> { + RpcStatus status; + Throwable t = future.cause(); + if (t != null) { + log.error("Failed delivering RPC command to device!", t); + status = RpcStatus.FAILED; + } else if (request.getOneway()) { + status = RpcStatus.SUCCESSFUL; + } else { + status = RpcStatus.DELIVERED; + } + TransportProtos.ToDevicePersistedRpcResponseMsg msg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder() + .setRequestId(request.getRequestId()) + .setRequestIdLSB(request.getRequestIdLSB()) + .setRequestIdMSB(request.getRequestIdMSB()) + .setStatus(status.name()) + .build(); + transportService.process(getSessionInfo(), msg, TransportServiceCallback.EMPTY); + }); + } + } + ); } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java index 7882ad2410..38d4232492 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionHandler.java @@ -28,6 +28,7 @@ import com.google.gson.JsonSyntaxException; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.ProtocolStringList; import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage; @@ -188,8 +189,8 @@ public class GatewaySessionHandler { } } - void writeAndFlush(MqttMessage mqttMessage) { - channel.writeAndFlush(mqttMessage); + ChannelFuture writeAndFlush(MqttMessage mqttMessage) { + return channel.writeAndFlush(mqttMessage); } int nextMsgId() { @@ -251,7 +252,7 @@ public class GatewaySessionHandler { new TransportServiceCallback() { @Override public void onSuccess(GetOrCreateDeviceFromGatewayResponse msg) { - GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap); + GatewayDeviceSessionCtx deviceSessionCtx = new GatewayDeviceSessionCtx(GatewaySessionHandler.this, msg.getDeviceInfo(), msg.getDeviceProfile(), mqttQoSMap, transportService); if (devices.putIfAbsent(deviceName, deviceSessionCtx) == null) { log.trace("[{}] First got or created device [{}], type [{}] for the gateway session", sessionId, deviceName, deviceType); SessionInfoProto deviceSessionInfo = deviceSessionCtx.getSessionInfo(); 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 ce70ece5ff..da459214c0 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,7 +26,9 @@ 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.rpc.RpcStatus; import org.thingsboard.server.common.transport.SessionMsgListener; +import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -139,6 +141,21 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); + if (toDeviceRequest.getPersisted()) { + RpcStatus status; + if (toDeviceRequest.getOneway()) { + status = RpcStatus.SUCCESSFUL; + } else { + status = RpcStatus.DELIVERED; + } + TransportProtos.ToDevicePersistedRpcResponseMsg responseMsg = TransportProtos.ToDevicePersistedRpcResponseMsg.newBuilder() + .setRequestId(toDeviceRequest.getRequestId()) + .setRequestIdLSB(toDeviceRequest.getRequestIdLSB()) + .setRequestIdMSB(toDeviceRequest.getRequestIdMSB()) + .setStatus(status.name()) + .build(); + snmpTransportContext.getTransportService().process(getSessionInfo(), responseMsg, TransportServiceCallback.EMPTY); + } } @Override 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 4a4e68f64f..e692d3b002 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 @@ -20,6 +20,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.service.SessionMetaData; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; @@ -109,6 +110,8 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback); + void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDevicePersistedRpcResponseMsg msg, TransportServiceCallback callback); + void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback callback); void process(SessionInfoProto sessionInfo, ClaimDeviceMsg 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 77b75dc07a..b4e536a49b 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 @@ -557,6 +557,15 @@ public class DefaultTransportService implements TransportService { } } + @Override + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDevicePersistedRpcResponseMsg msg, TransportServiceCallback callback) { + if (checkLimits(sessionInfo, msg, callback)) { + reportActivityInternal(sessionInfo); + sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo).setPersistedRpcResponseMsg(msg).build(), + new ApiStatsProxyCallback<>(getTenantId(sessionInfo), getCustomerId(sessionInfo), 1, callback)); + } + } + private void processTimeout(String requestId) { RpcRequestMetadata data = toServerRpcPendingMap.remove(requestId); if (data != null) { 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 bfb530dd35..483841f4fb 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 @@ -496,6 +496,17 @@ public class ModelConstants { public static final String OTA_PACKAGE_DATA_SIZE_COLUMN = "data_size"; public static final String OTA_PACKAGE_ADDITIONAL_INFO_COLUMN = ADDITIONAL_INFO_PROPERTY; + /** + * Persisted RPC constants. + */ + public static final String RPC_TABLE_NAME = "rpc"; + public static final String RPC_TENANT_ID_COLUMN = TENANT_ID_COLUMN; + public static final String RPC_DEVICE_ID = "device_id"; + public static final String RPC_EXPIRATION_TIME = "expiration_time"; + public static final String RPC_REQUEST = "request"; + public static final String RPC_RESPONSE = "response"; + public static final String RPC_STATUS = "status"; + /** * Edge constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java new file mode 100644 index 0000000000..a8823cb8cd --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/RpcEntity.java @@ -0,0 +1,102 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 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.id.DeviceId; +import org.thingsboard.server.common.data.id.RpcId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.BaseSqlEntity; +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.util.UUID; + +import static org.thingsboard.server.dao.model.ModelConstants.RPC_DEVICE_ID; +import static org.thingsboard.server.dao.model.ModelConstants.RPC_EXPIRATION_TIME; +import static org.thingsboard.server.dao.model.ModelConstants.RPC_REQUEST; +import static org.thingsboard.server.dao.model.ModelConstants.RPC_RESPONSE; +import static org.thingsboard.server.dao.model.ModelConstants.RPC_STATUS; +import static org.thingsboard.server.dao.model.ModelConstants.RPC_TABLE_NAME; +import static org.thingsboard.server.dao.model.ModelConstants.RPC_TENANT_ID_COLUMN; + +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = RPC_TABLE_NAME) +public class RpcEntity extends BaseSqlEntity implements BaseEntity { + + @Column(name = RPC_TENANT_ID_COLUMN) + private UUID tenantId; + + @Column(name = RPC_DEVICE_ID) + private UUID deviceId; + + @Column(name = RPC_EXPIRATION_TIME) + private long expirationTime; + + @Type(type = "json") + @Column(name = RPC_REQUEST) + private JsonNode request; + + @Type(type = "json") + @Column(name = RPC_RESPONSE) + private JsonNode response; + + @Enumerated(EnumType.STRING) + @Column(name = RPC_STATUS) + private RpcStatus status; + + public RpcEntity() { + super(); + } + + public RpcEntity(Rpc rpc) { + this.setUuid(rpc.getUuidId()); + this.createdTime = rpc.getCreatedTime(); + this.tenantId = rpc.getTenantId().getId(); + this.deviceId = rpc.getDeviceId().getId(); + this.expirationTime = rpc.getExpirationTime(); + this.request = rpc.getRequest(); + this.response = rpc.getResponse(); + this.status = rpc.getStatus(); + } + + @Override + public Rpc toData() { + Rpc rpc = new Rpc(new RpcId(id)); + rpc.setCreatedTime(createdTime); + rpc.setTenantId(new TenantId(tenantId)); + rpc.setDeviceId(new DeviceId(deviceId)); + rpc.setExpirationTime(expirationTime); + rpc.setRequest(request); + rpc.setResponse(response); + rpc.setStatus(status); + return rpc; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java b/dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java new file mode 100644 index 0000000000..577263a3bd --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.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.dao.rpc; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RpcId; +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.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; + +@Service +@Slf4j +@RequiredArgsConstructor +public class BaseRpcService implements RpcService { + private final RpcDao rpcDao; + + @Override + public Rpc save(TenantId tenantId, Rpc rpc) { + return rpcDao.save(tenantId, rpc); + } + + @Override + public void remove(TenantId tenantId, RpcId id) { + rpcDao.removeById(tenantId, id.getId()); + } + + @Override + public Rpc findById(TenantId tenantId, RpcId id) { + return rpcDao.findById(tenantId, id.getId()); + } + + @Override + public ListenableFuture findRpcByIdAsync(TenantId tenantId, RpcId id) { + return rpcDao.findByIdAsync(tenantId, id.getId()); + } + + @Override + public PageData findAllByDeviceIdAndStatus(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { + return rpcDao.findAllByDeviceId(deviceId, rpcStatus, pageLink); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java new file mode 100644 index 0000000000..753d1043f3 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.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.dao.rpc; + +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.dao.Dao; + +public interface RpcDao extends Dao { + PageData findAllByDeviceId(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java new file mode 100644 index 0000000000..1a538c9d9d --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.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.dao.sql.rpc; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.RpcEntity; +import org.thingsboard.server.dao.rpc.RpcDao; +import org.thingsboard.server.dao.sql.JpaAbstractDao; + +import java.util.UUID; + +@Slf4j +@Component +@AllArgsConstructor +public class JpaRpcDao extends JpaAbstractDao implements RpcDao { + + private final RpcRepository rpcRepository; + + @Override + protected Class getEntityClass() { + return RpcEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return rpcRepository; + } + + + @Override + public PageData findAllByDeviceId(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { + return DaoUtil.toPageData(rpcRepository.findAllByDeviceIdAndStatus(deviceId.getId(), rpcStatus, DaoUtil.toPageable(pageLink))); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java new file mode 100644 index 0000000000..bc04aa7e71 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.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.dao.sql.rpc; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.repository.CrudRepository; +import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.dao.model.sql.RpcEntity; + +import java.util.UUID; + +public interface RpcRepository extends CrudRepository { + Page findAllByDeviceIdAndStatus(UUID deviceId, RpcStatus status, Pageable pageable); +} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 2c5113867a..65fe412a9a 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -503,3 +503,14 @@ CREATE TABLE IF NOT EXISTS edge_event ( tenant_id uuid, ts bigint NOT NULL ); + +CREATE TABLE IF NOT EXISTS rpc ( + id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid NOT NULL, + device_id uuid NOT NULL, + expiration_time bigint NOT NULL, + request varchar(10000000) NOT NULL, + response varchar(10000000), + status varchar(255) NOT NULL +); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 3140ff83d6..1d70bf9f3a 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -541,6 +541,17 @@ CREATE TABLE IF NOT EXISTS edge_event ( ts bigint NOT NULL ); +CREATE TABLE IF NOT EXISTS rpc ( + id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid NOT NULL, + device_id uuid NOT NULL, + expiration_time bigint NOT NULL, + request varchar(10000000) NOT NULL, + response varchar(10000000), + status varchar(255) NOT NULL +); + CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ 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 726b4ba412..57090d34ad 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -32,4 +32,5 @@ DROP TABLE IF EXISTS resource; DROP TABLE IF EXISTS ota_package; DROP TABLE IF EXISTS edge; DROP TABLE IF EXISTS edge_event; +DROP TABLE IF EXISTS rpc; 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 855a53df2d..a641fff27e 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -33,3 +33,4 @@ DROP TABLE IF EXISTS resource; DROP TABLE IF EXISTS firmware; DROP TABLE IF EXISTS edge; DROP TABLE IF EXISTS edge_event; +DROP TABLE IF EXISTS rpc; diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java index 856dbf8e60..1d74040b34 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineDeviceRpcRequest.java @@ -35,6 +35,7 @@ public final class RuleEngineDeviceRpcRequest { private final UUID requestUUID; private final String originServiceId; private final boolean oneway; + private final boolean persisted; private final String method; private final String body; private final long expirationTime; 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 31bd913f47..1867ae4805 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 @@ -81,6 +81,9 @@ public class TbSendRPCRequestNode implements TbNode { tmp = msg.getMetaData().getValue("oneway"); boolean oneway = !StringUtils.isEmpty(tmp) && Boolean.parseBoolean(tmp); + tmp = msg.getMetaData().getValue("persisted"); + boolean persisted = !StringUtils.isEmpty(tmp) && Boolean.parseBoolean(tmp); + tmp = msg.getMetaData().getValue("requestUUID"); UUID requestUUID = !StringUtils.isEmpty(tmp) ? UUID.fromString(tmp) : Uuids.timeBased(); tmp = msg.getMetaData().getValue("originServiceId"); @@ -108,6 +111,7 @@ public class TbSendRPCRequestNode implements TbNode { .originServiceId(originServiceId) .expirationTime(expirationTime) .restApiCall(restApiCall) + .persisted(persisted) .build(); ctx.getRpcService().sendRpcRequestToDevice(request, ruleEngineDeviceRpcResponse -> { From f88cfee2b0f28737b9b0b8a271e7bcd01b980cd5 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 11 Jun 2021 12:29:41 +0300 Subject: [PATCH 040/231] Refactoring of the Downlink Part 2 --- .../server/DefaultLwM2MUplinkMsgHandler.java | 123 ++- .../DefaultLwM2mDownlinkMsgHandler.java | 970 ++++++++---------- .../lwm2m/server/LwM2mDownlinkMsgHandler.java | 50 +- .../lwm2m/server/LwM2mUplinkMsgHandler.java | 2 - .../lwm2m/server/client/LwM2mClient.java | 9 +- .../lwm2m/server/client/LwM2mFwSwUpdate.java | 46 +- .../AbstractTbLwM2MRequestCallback.java | 40 + ...bstractTbLwM2MTargetedDownlinkRequest.java | 32 + .../downlink/DownlinkRequestCallback.java | 27 +- .../server/downlink/HasContentFormat.java | 23 + .../lwm2m/server/downlink/HasVersionedId.java | 28 + ...bLwM2MCancelAllObserveRequestCallback.java | 36 + .../downlink/TbLwM2MCancelAllRequest.java | 38 + .../downlink/TbLwM2MCancelObserveRequest.java | 37 + .../TbLwM2MCancelObserveRequestCallback.java | 38 + .../server/downlink/TbLwM2MDeleteRequest.java | 37 + .../TbLwM2MDeleteRequestCallback.java | 37 + .../downlink/TbLwM2MDiscoverAllRequest.java | 40 + .../downlink/TbLwM2MDiscoverCallback.java | 37 + .../downlink/TbLwM2MDiscoverRequest.java | 37 + .../downlink/TbLwM2MDownlinkRequest.java | 26 + .../downlink/TbLwM2MExecuteRequest.java | 41 + .../TbLwM2MExecuteRequestCallback.java | 37 + .../downlink/TbLwM2MObserveAllRequest.java | 41 + .../downlink/TbLwM2MObserveRequest.java | 43 + .../TbLwM2MObserveRequestCallback.java | 37 + .../server/downlink/TbLwM2MReadRequest.java | 42 + .../downlink/TbLwM2MReadRequestCallback.java | 37 + .../TbLwM2MWriteAttributesCallback.java | 38 + .../TbLwM2MWriteAttributesRequest.java | 44 + .../downlink/TbLwM2MWriteReplaceCallback.java | 37 + .../downlink/TbLwM2MWriteReplaceRequest.java | 43 + .../downlink/TbLwM2MWriteUpdateRequest.java | 45 + 33 files changed, 1569 insertions(+), 629 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MTargetedDownlinkRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveRequestCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequestCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequestCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequestCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequestCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequestCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceCallback.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java index 45e07eaf4c..e6c14b0ba0 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java @@ -29,6 +29,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.observation.Observation; +import org.eclipse.leshan.core.request.CancelObservationRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; @@ -66,6 +67,22 @@ 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.TbLwM2MCancelAllObserveRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequestCallback; +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.TbLwM2MObserveAllRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequestCallback; +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.TbLwM2MWriteReplaceRequest; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -99,11 +116,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_TELEMETRY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_WARN; -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_CANCEL_ALL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.READ; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; 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; @@ -283,18 +296,18 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { //TODO: associate endpointId with device information. } - /** - * Cancel observation for All objects for this registration - */ - @Override - public void setCancelObservationsAll(Registration registration) { - if (registration != null) { - LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); - if (client != null && client.getRegistration() != null && client.getRegistration().getId().equals(registration.getId())) { - defaultLwM2MDownlinkMsgHandler.sendCancelAllRequest(client, this.config.getTimeout(), null); - } - } - } +// /** +// * Cancel observation for All objects for this registration +// */ +// @Override +// public void setCancelObservationsAll(Registration registration) { +// if (registration != null) { +// LwM2mClient client = clientContext.getClientByEndpoint(registration.getEndpoint()); +// if (client != null && client.getRegistration() != null && client.getRegistration().getId().equals(registration.getId())) { +// defaultLwM2MDownlinkMsgHandler.sendCancelAllRequest(client, TbLwM2MCancelAllRequest.builder().build(), new TbLwM2MCancelAllObserveRequestCallback(this, client)); +// } +// } +// } /** * Sending observe value to thingsboard from ObservationListener.onResponse: object, instance, SingleResource or MultipleResource @@ -321,6 +334,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { } } if (rpcRequest != null) { + //TODO: move this to separate callback. this.sendRpcRequestAfterReadResponse(registration, lwM2MClient, path, response, rpcRequest); } } @@ -466,10 +480,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); } else { - defaultLwM2MDownlinkMsgHandler.sendAllRequest(client, lwm2mClientRpcRequest.getTargetIdVer(), lwm2mClientRpcRequest.getTypeOper(), - null, - lwm2mClientRpcRequest.getValue() == null ? lwm2mClientRpcRequest.getParams() : lwm2mClientRpcRequest.getValue(), - this.config.getTimeout(), lwm2mClientRpcRequest); + //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); @@ -594,7 +609,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (LwM2mTransportUtil.LwM2MClientStrategy.CLIENT_STRATEGY_2.code == profile.getClientLwM2mSettings().getClientOnlyObserveAfterConnect()) { // #2 lwM2MClient.getPendingReadRequests().addAll(supportedObjects); - supportedObjects.forEach(path -> defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, path, this.config.getTimeout())); + supportedObjects.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId)); } // #1 this.sendReadRequests(lwM2MClient, profile, supportedObjects); @@ -610,7 +625,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { targetIds.addAll(profile.getObserveAttr().getTelemetry()); targetIds = targetIds.stream().filter(target -> isSupportedTargetId(supportedObjects, target)).collect(Collectors.toSet()); lwM2MClient.getPendingReadRequests().addAll(targetIds); - targetIds.forEach(targetId -> defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, targetId, this.config.getTimeout())); + targetIds.forEach(versionedId -> sendReadRequest(lwM2MClient, versionedId)); } private void sendObserveRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { @@ -618,7 +633,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { 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 -> defaultLwM2MDownlinkMsgHandler.sendObserveRequest(lwM2MClient, targetId, this.config.getTimeout())); + targetIds.forEach(targetId -> sendObserveRequest(lwM2MClient, targetId)); } private void sendWriteAttributeRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { @@ -626,16 +641,40 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { attributesMap = attributesMap.entrySet().stream().filter(target -> isSupportedTargetId(supportedObjects, target.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); // TODO: why do we need to put observe into pending read requests? // lwM2MClient.getPendingReadRequests().addAll(targetIds); - attributesMap.forEach((targetId, params) -> defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(lwM2MClient, targetId, params, this.config.getTimeout())); + attributesMap.forEach((targetId, params) -> sendWriteAttributesRequest(lwM2MClient, targetId, params)); } - private void sendDiscoverRequests(LwM2mClient lwM2MClient, Lwm2mDeviceProfileTransportConfiguration profile, Set supportedObjects) { Set targetIds = profile.getObserveAttr().getAttributeLwm2m().keySet(); 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 -> defaultLwM2MDownlinkMsgHandler.sendDiscoverRequest(lwM2MClient, targetId, this.config.getTimeout())); + targetIds.forEach(targetId -> sendDiscoverRequest(lwM2MClient, targetId)); + } + + 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)); + } + + private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) { + TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, request, new TbLwM2MReadRequestCallback(this, lwM2MClient, versionedId)); + } + + private void sendObserveRequest(LwM2mClient lwM2MClient, String versionedId) { + TbLwM2MObserveRequest request = TbLwM2MObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + defaultLwM2MDownlinkMsgHandler.sendObserveRequest(lwM2MClient, request, new TbLwM2MObserveRequestCallback(this, lwM2MClient, versionedId)); + } + + 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)); + } + + private void sendCancelObserveRequest(String versionedId, LwM2mClient client) { + TbLwM2MCancelObserveRequest request = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); + defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveRequestCallback(this, client, versionedId)); } /** @@ -690,7 +729,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if ((convertPathFromObjectIdToIdVer(FW_RESULT_ID, registration).equals(path)) || (convertPathFromObjectIdToIdVer(FW_STATE_ID, registration).equals(path))) { - LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(clientContext); + LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(this, clientContext); log.warn("93) path: [{}] value: [{}]", path, lwM2mResource.getValue()); fwUpdate.updateStateOta(this, defaultLwM2MDownlinkMsgHandler, registration, path, ((Long) lwM2mResource.getValue()).intValue()); } @@ -935,27 +974,27 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { if (!newObjectsToRead.isEmpty()) { Set newObjectsToReadButNotNewInObserve = diffSets(observeToAdd, newObjectsToRead); // update value in Resources - for (String targetId : newObjectsToReadButNotNewInObserve) { - clients.forEach(client -> defaultLwM2MDownlinkMsgHandler.sendReadRequest(client, targetId, this.config.getTimeout())); + for (String versionedId : newObjectsToReadButNotNewInObserve) { + clients.forEach(client -> sendReadRequest(client, versionedId)); } } // Calculating difference between old and new flags. if (!observeToAdd.isEmpty()) { for (String targetId : observeToAdd) { - clients.forEach(client -> defaultLwM2MDownlinkMsgHandler.sendObserveRequest(client, targetId, this.config.getTimeout())); + clients.forEach(client -> sendObserveRequest(client, targetId)); } } if (!observeToRemove.isEmpty()) { for (String targetId : observeToRemove) { - clients.forEach(client -> defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, targetId, this.config.getTimeout(), null)); + clients.forEach(client -> sendCancelObserveRequest(targetId, client)); } } } } /** - Returns new set with elements that are present in set B(new) but absent in set A(old). + * Returns new set with elements that are present in set B(new) but absent in set A(old). */ private static Set diffSets(Set a, Set b) { return b.stream().filter(p -> !a.contains(p)).collect(Collectors.toSet()); @@ -974,7 +1013,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { /** * #6.1 - send update WriteAttribute * #6.2 - send empty WriteAttribute - * */ private void compareAndSendWriteAttributes(List clients, Map lwm2mAttributesOld, Map lwm2mAttributesNew) { ParametersAnalyzeResult analyzerParameters = new ParametersAnalyzeResult(); @@ -997,7 +1035,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { Set pathSend = analyzerParameters.getPathPostParametersAdd().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { - pathSend.forEach(target -> defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(client, target, lwm2mAttributesNew.get(target), this.config.getTimeout())); + pathSend.forEach(target -> sendWriteAttributesRequest(client, target, lwm2mAttributesNew.get(target))); } }); } @@ -1008,21 +1046,22 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { Set pathSend = analyzerParameters.getPathPostParametersDel().stream().filter(target -> clientObjects.contains("/" + target.split(LWM2M_SEPARATOR_PATH)[1])) .collect(Collectors.toUnmodifiableSet()); if (!pathSend.isEmpty()) { - pathSend.forEach(target -> defaultLwM2MDownlinkMsgHandler.sendWriteAttributesRequest(client, target, new ObjectAttributes(), this.config.getTimeout())); + pathSend.forEach(target -> sendWriteAttributesRequest(client, target, new ObjectAttributes())); } }); } } - private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String path) { + private void updateResourcesValueToClient(LwM2mClient lwM2MClient, Object valueOld, Object newValue, String versionedId) { if (newValue != null && (valueOld == null || !newValue.toString().equals(valueOld.toString()))) { - defaultLwM2MDownlinkMsgHandler.sendWriteReplaceRequest(lwM2MClient, path, newValue, this.config.getTimeout(), null); + TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId).value(newValue).timeout(this.config.getTimeout()).build(); + defaultLwM2MDownlinkMsgHandler.sendWriteReplaceRequest(lwM2MClient, request, new TbLwM2MWriteReplaceCallback(this, lwM2MClient, versionedId)); } else { - log.error("Failed update resource [{}] [{}]", path, newValue); - String logMsg = String.format("%s: Failed update resource path - %s value - %s. Value is not changed or bad", - LOG_LW2M_ERROR, path, newValue); + 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.info("Failed update resource [{}] [{}]", path, newValue); + log.info("Failed update resource [{}] [{}]", versionedId, newValue); } } @@ -1176,7 +1215,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus()) && response.getType().equals(OtaPackageType.FIRMWARE.name())) { - LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(clientContext); + LwM2mFwSwUpdate fwUpdate = lwM2MClient.getFwUpdate(DefaultLwM2MUplinkMsgHandler.this, clientContext); if (rpcRequest != null) { fwUpdate.setStateUpdate(INITIATED.name()); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java index 3ba3de2b75..b1b52ec3ed 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java @@ -17,18 +17,12 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.eclipse.californium.core.coap.CoAP; -import org.eclipse.californium.core.coap.Response; import org.eclipse.leshan.core.Link; import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; import org.eclipse.leshan.core.model.ResourceModel; -import org.eclipse.leshan.core.node.LwM2mNode; -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.node.ObjectLink; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.observation.Observation; @@ -41,13 +35,12 @@ import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.request.WriteRequest; -import org.eclipse.leshan.core.request.exception.ClientSleepingException; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; import org.eclipse.leshan.core.response.LwM2mResponse; +import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadResponse; -import org.eclipse.leshan.core.response.ResponseCallback; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.eclipse.leshan.core.util.Hex; @@ -61,6 +54,19 @@ 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.server.downlink.DownlinkRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; +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.TbLwM2MDiscoverRequest; +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.TbLwM2MObserveRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; +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.TbLwM2MWriteUpdateRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; @@ -70,43 +76,18 @@ import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; 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; -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.eclipse.leshan.core.attributes.Attribute.GREATER_THAN; 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.common.data.ota.OtaPackageUpdateStatus.DOWNLOADED; -import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.FAILED; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.DEFAULT_TIMEOUT; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FW_PACKAGE_5_ID; -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_LW2M_VALUE; -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_CANCEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; -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.RESPONSE_REQUEST_CHANNEL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_INSTALL_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.SW_PACKAGE_ID; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertPathFromObjectIdToIdVer; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.createWriteAttributeRequest; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; @Slf4j @Service @@ -120,7 +101,6 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { private final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; private final LwM2mClientContext lwM2mClientContext; - private final DefaultLwM2MUplinkMsgHandler handler; @PostConstruct public void init() { @@ -130,100 +110,90 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } @Override - public void sendReadRequest(LwM2mClient client, String targetId, Long timeout) { - sendReadRequest(client, targetId, client.getDefaultContentFormat(), timeout); - } - - @Override - public void sendReadRequest(LwM2mClient client, String targetId, ContentFormat contentFormat, Long timeout) { - String objectId = fromVersionedIdToObjectId(targetId); - if (objectId != null && client.isValidObjectVersion(targetId)) { - sendRequest(client, new ReadRequest(contentFormat, objectId), timeout); + 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); } } @Override - public void sendObserveRequest(LwM2mClient client, String targetId, Long timeout) { - sendObserveRequest(client, targetId, client.getDefaultContentFormat(), timeout); - } - - @Override - public void sendObserveRequest(LwM2mClient client, String targetId, ContentFormat contentFormat, Long timeout) { - String objectId = fromVersionedIdToObjectId(targetId); - if (objectId != null && client.isValidObjectVersion(targetId)) { - LwM2mPath resultIds = new LwM2mPath(objectId); + 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 request; + ObserveRequest downlink; + ContentFormat contentFormat = getContentFormat(client, request); if (resultIds.isResource()) { - request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); } else if (resultIds.isObjectInstance()) { - request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); } else { - request = new ObserveRequest(contentFormat, resultIds.getObjectId()); + downlink = new ObserveRequest(contentFormat, resultIds.getObjectId()); } - log.info("[{}] Send observation: {}.", client.getEndpoint(), targetId); - sendRequest(client, request, timeout); + log.info("[{}] Send observation: {}.", client.getEndpoint(), request.getVersionedId()); + sendRequest(client, downlink, request.getTimeout(), callback); } } } @Override - public void sendExecuteRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback) { - sendExecuteRequest(client, targetId, null, timeout, 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); } @Override - public void sendExecuteRequest(LwM2mClient client, String targetId, Object params, Long timeout, DownlinkRequestCallback callback) { - String target = fromVersionedIdToObjectId(targetId); - LwM2mPath resultIds = new LwM2mPath(target); - ResourceModel resourceModelExecute = client.getResourceModel(targetId, this.config.getModelProvider()); + 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); + } + + @Override + public void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback) { + ResourceModel resourceModelExecute = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); if (resourceModelExecute != null) { - ExecuteRequest request; - if (params != null && !resourceModelExecute.multiple) { - request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModelExecute.type, ResourceModel.Type.STRING, resultIds)); + ExecuteRequest downlink; + if (request.getParams() != null && !resourceModelExecute.multiple) { + downlink = new ExecuteRequest(request.getVersionedId(), (String) this.converter.convertValue(request.getParams(), resourceModelExecute.type, ResourceModel.Type.STRING, new LwM2mPath(request.getObjectId()))); } else { - request = new ExecuteRequest(target); + downlink = new ExecuteRequest(request.getVersionedId()); } - sendRequest(client, request, timeout); + sendRequest(client, downlink, request.getTimeout(), callback); } } - public void sendDeleteRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback) { - String target = fromVersionedIdToObjectId(targetId); - sendRequest(client, new DeleteRequest(target), timeout); + @Override + public void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback callback) { + sendRequest(client, new DeleteRequest(request.getObjectId()), request.getTimeout(), callback); } @Override - public void sendCancelObserveRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback) { - int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), fromVersionedIdToObjectId(targetId)); - String observeCancelMsg = String.format("%s: type operation %s paths: %s count: %d", LOG_LW2M_INFO, OBSERVE_CANCEL.name(), targetId, observeCancelCnt); - if (callback != null) { - callback.onSuccess(client, observeCancelMsg); - } + public void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback) { + int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration(), request.getObjectId()); + callback.onSuccess(observeCancelCnt); } @Override - public void sendCancelAllRequest(LwM2mClient client, Long timeout, DownlinkRequestCallback callback) { + public void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback callback) { int observeCancelCnt = context.getServer().getObservationService().cancelObservations(client.getRegistration()); - String observeCancelMsg = String.format("%s: type operation %s paths: All count: %d", LOG_LW2M_INFO, OBSERVE_CANCEL.name(), observeCancelCnt); - if (callback != null) { - callback.onSuccess(client, observeCancelMsg); - } + callback.onSuccess(observeCancelCnt); } @Override - public void sendDiscoverRequest(LwM2mClient client, String targetId, Long timeout) { - String objectId = fromVersionedIdToObjectId(targetId); - if (objectId != null && client.isValidObjectVersion(targetId)) { - sendRequest(client, new DiscoverRequest(objectId), timeout); + 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); } } @Override - public void sendWriteAttributesRequest(LwM2mClient client, String targetId, ObjectAttributes params, Long timeout) { - String objectId = fromVersionedIdToObjectId(targetId); - if (objectId != null && client.isValidObjectVersion(targetId) && params != null) { + 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<>(); // Dimension and Object version are read only attributes. // addAttribute(attributes, DIMENSION, params.getDim(), dim -> dim >= 0 && dim <= 255); @@ -234,366 +204,233 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { addAttribute(attributes, LESSER_THAN, params.getLt()); addAttribute(attributes, STEP, params.getSt()); AttributeSet attributeSet = new AttributeSet(attributes); - sendRequest(client, new WriteAttributesRequest(objectId, attributeSet), timeout); + sendRequest(client, new WriteAttributesRequest(request.getObjectId(), attributeSet), request.getTimeout(), callback); } } @Override - public void sendWriteReplaceRequest(LwM2mClient client, String targetIdVer, Object newValue, Long timeout, DownlinkRequestCallback callback) { - ResourceModel resourceModelWrite = client.getResourceModel(targetIdVer, this.config.getModelProvider()); + 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); try { - LwM2mPath path = new LwM2mPath(fromVersionedIdToObjectId(targetIdVer)); - WriteRequest request = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, - path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), newValue); - sendRequest(client, request, timeout); + LwM2mPath path = new LwM2mPath(request.getObjectId()); + WriteRequest downlink = this.getWriteRequestSingleResource(resourceModelWrite.type, contentFormat, + path.getObjectId(), path.getObjectInstanceId(), path.getResourceId(), request.getValue()); + sendRequest(client, downlink, request.getTimeout(), callback); } catch (Exception e) { - callback.onError(client, e.getMessage(), e); + callback.onError(e); } + } else { + //TODO: log validation error using callback. } } @Override - public void sendWriteUpdateRequest(LwM2mClient client, String targetIdVer, Object newValue, ContentFormat contentFormat, Long timeout, DownlinkRequestCallback callback) { - LwM2mPath resultIds = new LwM2mPath(fromVersionedIdToObjectId(targetIdVer)); + public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback) { + LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); if (resultIds.isResource()) { /* * send request: path = '/3/0' node == wM2mObjectInstance * with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} **/ - Collection resources = client.getNewResourceForInstance(targetIdVer, newValue, this.config.getModelProvider(), this.converter); - ResourceModel resourceModelWrite = client.getResourceModel(targetIdVer, this.config.getModelProvider()); - WriteRequest request = new WriteRequest(WriteRequest.Mode.UPDATE, convertResourceModelTypeToContentFormat(client, resourceModelWrite.type), resultIds.getObjectId(), + 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(), resultIds.getObjectInstanceId(), resources); - sendRequest(client, request, timeout); + sendRequest(client, downlink, request.getTimeout(), callback); } else if (resultIds.isObjectInstance()) { /* * params = "{\"id\":0,\"resources\":[{\"id\":14,\"value\":\"+5\"},{\"id\":15,\"value\":\"+9\"}]}" * int rscId = resultIds.getObjectInstanceId(); * contentFormat – Format of the payload (TLV or JSON). */ - Collection resources = client.getNewResourcesForInstance(targetIdVer, newValue, this.config.getModelProvider(), this.converter); + Collection resources = client.getNewResourcesForInstance(request.getVersionedId(), request.getValue(), this.config.getModelProvider(), this.converter); if (resources.size() > 0) { - contentFormat = contentFormat.equals(ContentFormat.JSON) ? contentFormat : client.getDefaultContentFormat(); - WriteRequest request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); - sendRequest(client, request, timeout); + ContentFormat contentFormat = request.getObjectContentFormat() != null ? request.getObjectContentFormat() : client.getDefaultContentFormat(); + WriteRequest downlink = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resources); + sendRequest(client, downlink, request.getTimeout(), callback); } else { - callback.onError(client, "No resources to update!", new IllegalArgumentException()); + callback.onValidationError("No resources to update!"); } } else { - callback.onError(client, "Update of the root level object is not supported yet", new IllegalArgumentException()); - } - } - - private void addAttribute(List attributes, String attributeName, T value) { - addAttribute(attributes, attributeName, value, null, null); - } - - private void addAttribute(List attributes, String attributeName, T value, Function converter) { - addAttribute(attributes, attributeName, value, null, converter); - } - - private void addAttribute(List attributes, String attributeName, T value, Predicate filter, Function converter) { - if (value != null && ((filter == null) || filter.test(value))) { - attributes.add(new Attribute(attributeName, converter != null ? converter.apply(value) : value)); - } - } - - private static ContentFormat convertResourceModelTypeToContentFormat(LwM2mClient client, ResourceModel.Type type) { - switch (type) { - case BOOLEAN: - case STRING: - case TIME: - case INTEGER: - case FLOAT: - return client.getDefaultContentFormat(); - case OPAQUE: - return ContentFormat.OPAQUE; - case OBJLNK: - return ContentFormat.LINK; - default: + callback.onValidationError("Update of the root level object is not supported yet!"); } - throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type); - } - - public void sendAllRequest(LwM2mClient client, String targetIdVer, LwM2mTypeOper typeOper, Object params, long timeoutInMs, LwM2mClientRpcRequest lwm2mClientRpcRequest) { - sendAllRequest(client, targetIdVer, typeOper, client.getDefaultContentFormat(), params, timeoutInMs, lwm2mClientRpcRequest); - } - - 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 SimpleDownlinkRequest createRequest(Registration registration, LwM2mClient client, LwM2mTypeOper typeOper, - ContentFormat contentFormat, String target, String targetIdVer, - LwM2mPath resultIds, Object params, LwM2mClientRpcRequest rpcRequest) { - SimpleDownlinkRequest request = null; - switch (typeOper) { - case READ: - request = new ReadRequest(contentFormat, target); - break; - case DISCOVER: - request = new DiscoverRequest(target); - break; - case OBSERVE: - String msg = String.format("%s: Send Observation %s.", LOG_LW2M_INFO, targetIdVer); - log.warn(msg); - if (resultIds.isResource()) { - Set observations = context.getServer().getObservationService().getObservations(registration); - Set paths = observations.stream().filter(observation -> observation.getPath().equals(resultIds)).collect(Collectors.toSet()); - if (paths.size() == 0) { - request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId(), resultIds.getResourceId()); - } else { - request = new ReadRequest(contentFormat, target); - } - } else if (resultIds.isObjectInstance()) { - request = new ObserveRequest(contentFormat, resultIds.getObjectId(), resultIds.getObjectInstanceId()); - } else if (resultIds.getObjectId() >= 0) { - request = new ObserveRequest(contentFormat, resultIds.getObjectId()); - } - break; - case EXECUTE: - ResourceModel resourceModelExecute = client.getResourceModel(targetIdVer, this.config.getModelProvider()); - if (resourceModelExecute != null) { - if (params != null && !resourceModelExecute.multiple) { - request = new ExecuteRequest(target, (String) this.converter.convertValue(params, resourceModelExecute.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. - * Type from resourceModel -> STRING, INTEGER, FLOAT, BOOLEAN, OPAQUE, TIME, OBJLNK - * contentFormat -> TLV, TLV, TLV, TLV, OPAQUE, TLV, LINK - * JSON, TEXT; - **/ - ResourceModel resourceModelWrite = client.getResourceModel(targetIdVer, this.config.getModelProvider()); - if (resourceModelWrite != null) { -// contentFormat = getContentFormatByResourceModelType(resourceModelWrite, contentFormat); -// request = this.getWriteRequestSingleResource(contentFormat, resultIds.getObjectId(), -// resultIds.getObjectInstanceId(), resultIds.getResourceId(), params, resourceModelWrite.type, -// client, rpcRequest); - } - break; - case WRITE_UPDATE: - if (resultIds.isResource()) { - /** - * send request: path = '/3/0' node == wM2mObjectInstance - * with params == "\"resources\": {15: resource:{id:15. value:'+01'...}} - **/ - Collection resources = client.getNewResourceForInstance( - targetIdVer, params, - this.config.getModelProvider(), - this.converter); -// contentFormat = getContentFormatByResourceModelType(client.getResourceModel(targetIdVer, this.config.getModelProvider()), -// contentFormat); -// 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(); - * contentFormat – Format of the payload (TLV or JSON). - */ - else if (resultIds.isObjectInstance()) { - if (((ConcurrentHashMap) params).size() > 0) { - Collection resources = client.getNewResourcesForInstance( - targetIdVer, params, - this.config.getModelProvider(), - this.converter); - if (resources.size() > 0) { - contentFormat = contentFormat.equals(ContentFormat.JSON) ? contentFormat : ContentFormat.TLV; - request = new WriteRequest(WriteRequest.Mode.UPDATE, contentFormat, resultIds.getObjectId(), - resultIds.getObjectInstanceId(), resources); - } - } - } else if (resultIds.getObjectId() >= 0) { - request = new ObserveRequest(resultIds.getObjectId()); - } - break; - case WRITE_ATTRIBUTES: - request = createWriteAttributeRequest(target, params, this.handler); - break; - case DELETE: - request = new DeleteRequest(target); - break; - } - return request; } - private void sendRequest(LwM2mClient client, SimpleDownlinkRequest request, long timeoutInMs) { - try { - sendRequest(client, request, timeoutInMs, null); - } catch (ClientSleepingException e) { - //TODO: this may cause infinite loop / memory leak. - client.getQueuedRequests().add(() -> sendRequest(client, request, timeoutInMs, null)); - } - } +// 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); +// } +// } +// } - @SuppressWarnings({"error sendRequest"}) - private void sendRequest(LwM2mClient client, SimpleDownlinkRequest request, long timeoutInMs, LwM2mClientRpcRequest rpcRequest) { + private void sendRequest(LwM2mClient client, SimpleDownlinkRequest request, long timeoutInMs, DownlinkRequestCallback callback) { Registration registration = client.getRegistration(); - context.getServer().send(registration, request, timeoutInMs, (ResponseCallback) response -> { - - if (!client.isInit()) { - client.initReadValue(this.handler, convertPathFromObjectIdToIdVer(request.getPath().toString(), registration)); - } - 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()); + 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); + } 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 -> { - /** 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); - } + responseRequestExecutor.submit(() -> { + callback.onError(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); +// } }); } @@ -622,7 +459,9 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { } return new WriteRequest(contentFormat, objectId, instanceId, resourceId, valueRequest); default: + throw new IllegalArgumentException("Not supported type:" + type.name()); } + // TODO: throw exception and execute callback. //// if (rpcRequest != null) { //// String patn = "/" + objectId + "/" + instanceId + "/" + resourceId; @@ -663,158 +502,193 @@ public class DefaultLwM2mDownlinkMsgHandler implements LwM2mDownlinkMsgHandler { */ private void sendResponse(LwM2mClient lwM2mClient, String path, LwM2mResponse response, SimpleDownlinkRequest request, LwM2mClientRpcRequest rpcRequest) { - Registration registration = lwM2mClient.getRegistration(); - String pathIdVer = convertPathFromObjectIdToIdVer(path, registration); - String msgLog = ""; - if (response instanceof ReadResponse) { - handler.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest); - } else if (response instanceof DeleteResponse) { - log.warn("11) [{}] Path [{}] DeleteResponse", pathIdVer, response); - if (rpcRequest != null) { - rpcRequest.setInfoMsg(null); - handler.sentRpcResponse(rpcRequest, response.getCode().getName(), null, null); - } - } else if (response instanceof DiscoverResponse) { - String discoverValue = Link.serialize(((DiscoverResponse) response).getObjectLinks()); - msgLog = String.format("%s: type operation: %s path: %s value: %s", - LOG_LW2M_INFO, DISCOVER.name(), request.getPath().toString(), discoverValue); - handler.sendLogsToThingsboard(lwM2mClient, msgLog); - log.warn("DiscoverResponse: [{}]", (DiscoverResponse) response); - if (rpcRequest != null) { - handler.sentRpcResponse(rpcRequest, response.getCode().getName(), discoverValue, LOG_LW2M_VALUE); - } - } else if (response instanceof ExecuteResponse) { - msgLog = String.format("%s: type operation: %s path: %s", - LOG_LW2M_INFO, EXECUTE.name(), request.getPath().toString()); - log.warn("9) [{}] ", msgLog); - handler.sendLogsToThingsboard(lwM2mClient, msgLog); - if (rpcRequest != null) { - msgLog = String.format("Start %s path: %S. Preparation finished: %s", EXECUTE.name(), path, rpcRequest.getInfoMsg()); - rpcRequest.setInfoMsg(msgLog); - handler.sentRpcResponse(rpcRequest, response.getCode().getName(), path, LOG_LW2M_INFO); - } - - } else if (response instanceof WriteAttributesResponse) { - msgLog = String.format("%s: type operation: %s path: %s value: %s", - LOG_LW2M_INFO, WRITE_ATTRIBUTES.name(), request.getPath().toString(), ((WriteAttributesRequest) request).getAttributes().toString()); - handler.sendLogsToThingsboard(lwM2mClient, msgLog); - log.warn("12) [{}] Path [{}] WriteAttributesResponse", pathIdVer, response); - if (rpcRequest != null) { - handler.sentRpcResponse(rpcRequest, response.getCode().getName(), response.toString(), LOG_LW2M_VALUE); - } - } else if (response instanceof WriteResponse) { - msgLog = String.format("Type operation: Write path: %s", pathIdVer); - log.warn("10) [{}] response: [{}]", msgLog, response); - this.infoWriteResponse(lwM2mClient, response, request, rpcRequest); - handler.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request); - } +// Registration registration = lwM2mClient.getRegistration(); +// String pathIdVer = convertPathFromObjectIdToIdVer(path, registration); +// String msgLog = ""; +// if (response instanceof ReadResponse) { +// handler.onUpdateValueAfterReadResponse(registration, pathIdVer, (ReadResponse) response, rpcRequest); +// } else if (response instanceof DeleteResponse) { +// log.warn("11) [{}] Path [{}] DeleteResponse", pathIdVer, response); +// if (rpcRequest != null) { +// rpcRequest.setInfoMsg(null); +// handler.sentRpcResponse(rpcRequest, response.getCode().getName(), null, null); +// } +// } else if (response instanceof DiscoverResponse) { +// String discoverValue = Link.serialize(((DiscoverResponse) response).getObjectLinks()); +// msgLog = String.format("%s: type operation: %s path: %s value: %s", +// LOG_LW2M_INFO, DISCOVER.name(), request.getPath().toString(), discoverValue); +// handler.sendLogsToThingsboard(lwM2mClient, msgLog); +// log.warn("DiscoverResponse: [{}]", (DiscoverResponse) response); +// if (rpcRequest != null) { +// handler.sentRpcResponse(rpcRequest, response.getCode().getName(), discoverValue, LOG_LW2M_VALUE); +// } +// } else if (response instanceof ExecuteResponse) { +// msgLog = String.format("%s: type operation: %s path: %s", +// LOG_LW2M_INFO, EXECUTE.name(), request.getPath().toString()); +// log.warn("9) [{}] ", msgLog); +// handler.sendLogsToThingsboard(lwM2mClient, msgLog); +// if (rpcRequest != null) { +// msgLog = String.format("Start %s path: %S. Preparation finished: %s", EXECUTE.name(), path, rpcRequest.getInfoMsg()); +// rpcRequest.setInfoMsg(msgLog); +// handler.sentRpcResponse(rpcRequest, response.getCode().getName(), path, LOG_LW2M_INFO); +// } +// +// } else if (response instanceof WriteAttributesResponse) { +// msgLog = String.format("%s: type operation: %s path: %s value: %s", +// LOG_LW2M_INFO, WRITE_ATTRIBUTES.name(), request.getPath().toString(), ((WriteAttributesRequest) request).getAttributes().toString()); +// handler.sendLogsToThingsboard(lwM2mClient, msgLog); +// log.warn("12) [{}] Path [{}] WriteAttributesResponse", pathIdVer, response); +// if (rpcRequest != null) { +// handler.sentRpcResponse(rpcRequest, response.getCode().getName(), response.toString(), LOG_LW2M_VALUE); +// } +// } else if (response instanceof WriteResponse) { +// msgLog = String.format("Type operation: Write path: %s", pathIdVer); +// log.warn("10) [{}] response: [{}]", msgLog, response); +// this.infoWriteResponse(lwM2mClient, response, request, rpcRequest); +// handler.onWriteResponseOk(registration, pathIdVer, (WriteRequest) request); +// } } - private void infoWriteResponse(LwM2mClient lwM2mClient, LwM2mResponse response, SimpleDownlinkRequest - request, LwM2mClientRpcRequest rpcRequest) { - try { - Registration registration = lwM2mClient.getRegistration(); - LwM2mNode node = ((WriteRequest) request).getNode(); - String msg = null; - Object value; - if (node instanceof LwM2mObject) { - msg = String.format("%s: Update finished successfully: Lwm2m code - %d Source path: %s value: %s", - LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), ((LwM2mObject) node).toString()); - } else if (node instanceof LwM2mObjectInstance) { - msg = String.format("%s: Update finished successfully: Lwm2m code - %d Source path: %s value: %s", - LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), ((LwM2mObjectInstance) node).prettyPrint()); - } else if (node instanceof LwM2mSingleResource) { - 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())).trim(); - - } else { - valueLength = ((byte[]) singleResource.getValue()).length; - value = new String(Arrays.copyOf(((byte[]) singleResource.getValue()), - Math.min(valueLength, config.getLogMaxLength()))).trim(); - } - 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) { - handler.sendLogsToThingsboard(lwM2mClient, msg); - if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { - this.afterWriteSuccessFwSwUpdate(registration, request); - if (rpcRequest != null) { - rpcRequest.setInfoMsg(msg); - } - } else if (rpcRequest != null) { - handler.sentRpcResponse(rpcRequest, response.getCode().getName(), msg, LOG_LW2M_INFO); - } - } - } catch (Exception e) { - log.trace("Fail convert value from request to string. ", e); - } - } +// private void infoWriteResponse(LwM2mClient lwM2mClient, LwM2mResponse response, SimpleDownlinkRequest +// request, LwM2mClientRpcRequest rpcRequest) { +// try { +// Registration registration = lwM2mClient.getRegistration(); +// LwM2mNode node = ((WriteRequest) request).getNode(); +// String msg = null; +// Object value; +// if (node instanceof LwM2mObject) { +// msg = String.format("%s: Update finished successfully: Lwm2m code - %d Source path: %s value: %s", +// LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), ((LwM2mObject) node).toString()); +// } else if (node instanceof LwM2mObjectInstance) { +// msg = String.format("%s: Update finished successfully: Lwm2m code - %d Source path: %s value: %s", +// LOG_LW2M_INFO, response.getCode().getCode(), request.getPath().toString(), ((LwM2mObjectInstance) node).prettyPrint()); +// } else if (node instanceof LwM2mSingleResource) { +// 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())).trim(); +// +// } else { +// valueLength = ((byte[]) singleResource.getValue()).length; +// value = new String(Arrays.copyOf(((byte[]) singleResource.getValue()), +// Math.min(valueLength, config.getLogMaxLength()))).trim(); +// } +// 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) { +// handler.sendLogsToThingsboard(lwM2mClient, msg); +// if (request.getPath().toString().equals(FW_PACKAGE_5_ID) || request.getPath().toString().equals(SW_PACKAGE_ID)) { +// this.afterWriteSuccessFwSwUpdate(registration, request); +// if (rpcRequest != null) { +// rpcRequest.setInfoMsg(msg); +// } +// } else if (rpcRequest != null) { +// handler.sentRpcResponse(rpcRequest, response.getCode().getName(), msg, LOG_LW2M_INFO); +// } +// } +// } catch (Exception e) { +// log.trace("Fail convert value from request to string. ", e); +// } +// } /** * After finish operation FwSwUpdate Write (success): * fw_state/sw_state = DOWNLOADED * send operation Execute */ - private void afterWriteSuccessFwSwUpdate(Registration registration, SimpleDownlinkRequest request) { - LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); - if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && client.getFwUpdate() != null) { - client.getFwUpdate().setStateUpdate(DOWNLOADED.name()); - client.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); - } - if (request.getPath().toString().equals(SW_PACKAGE_ID) && client.getSwUpdate() != null) { - client.getSwUpdate().setStateUpdate(DOWNLOADED.name()); - client.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); - } - } +// private void afterWriteSuccessFwSwUpdate(Registration registration, SimpleDownlinkRequest request) { +// LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); +// if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && client.getFwUpdate() != null) { +// client.getFwUpdate().setStateUpdate(DOWNLOADED.name()); +// client.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); +// } +// if (request.getPath().toString().equals(SW_PACKAGE_ID) && client.getSwUpdate() != null) { +// client.getSwUpdate().setStateUpdate(DOWNLOADED.name()); +// client.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_INFO, null); +// } +// } /** * After finish operation FwSwUpdate Write (error): fw_state = FAILED */ - private void afterWriteFwSWUpdateError(Registration registration, SimpleDownlinkRequest request, String - msgError) { - LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); - if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && client.getFwUpdate() != null) { - client.getFwUpdate().setStateUpdate(FAILED.name()); - client.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); - } - if (request.getPath().toString().equals(SW_PACKAGE_ID) && client.getSwUpdate() != null) { - client.getSwUpdate().setStateUpdate(FAILED.name()); - client.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); - } +// private void afterWriteFwSWUpdateError(Registration registration, SimpleDownlinkRequest request, String +// msgError) { +// LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); +// if (request.getPath().toString().equals(FW_PACKAGE_5_ID) && client.getFwUpdate() != null) { +// client.getFwUpdate().setStateUpdate(FAILED.name()); +// client.getFwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); +// } +// if (request.getPath().toString().equals(SW_PACKAGE_ID) && client.getSwUpdate() != null) { +// client.getSwUpdate().setStateUpdate(FAILED.name()); +// client.getSwUpdate().sendLogs(this.handler, WRITE_REPLACE.name(), LOG_LW2M_ERROR, msgError); +// } +// } + +// private void afterExecuteFwSwUpdateError(Registration registration, SimpleDownlinkRequest request, String +// msgError) { +// LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); +// if (request.getPath().toString().equals(FW_UPDATE_ID) && client.getFwUpdate() != null) { +// client.getFwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); +// } +// if (request.getPath().toString().equals(SW_INSTALL_ID) && client.getSwUpdate() != null) { +// client.getSwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); +// } +// } + +// private void afterObserveCancel(LwM2mClient lwM2mClient, int observeCancelCnt, String +// observeCancelMsg, LwM2mClientRpcRequest rpcRequest) { +// handler.sendLogsToThingsboard(lwM2mClient, observeCancelMsg); +// log.warn("[{}]", observeCancelMsg); +// if (rpcRequest != null) { +// rpcRequest.setInfoMsg(String.format("Count: %d", observeCancelCnt)); +// handler.sentRpcResponse(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO); +// } +// } + + + private static void addAttribute(List attributes, String attributeName, T value) { + addAttribute(attributes, attributeName, value, null, null); } - private void afterExecuteFwSwUpdateError(Registration registration, SimpleDownlinkRequest request, String - msgError) { - LwM2mClient client = this.lwM2mClientContext.getClientByRegistrationId(registration.getId()); - if (request.getPath().toString().equals(FW_UPDATE_ID) && client.getFwUpdate() != null) { - client.getFwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); - } - if (request.getPath().toString().equals(SW_INSTALL_ID) && client.getSwUpdate() != null) { - client.getSwUpdate().sendLogs(this.handler, EXECUTE.name(), LOG_LW2M_ERROR, msgError); + private static void addAttribute(List attributes, String attributeName, T value, Function converter) { + addAttribute(attributes, attributeName, value, null, converter); + } + + private static void addAttribute(List attributes, String attributeName, T value, Predicate filter, Function converter) { + if (value != null && ((filter == null) || filter.test(value))) { + attributes.add(new Attribute(attributeName, converter != null ? converter.apply(value) : value)); } } - private void afterObserveCancel(LwM2mClient lwM2mClient, int observeCancelCnt, String - observeCancelMsg, LwM2mClientRpcRequest rpcRequest) { - handler.sendLogsToThingsboard(lwM2mClient, observeCancelMsg); - log.warn("[{}]", observeCancelMsg); - if (rpcRequest != null) { - rpcRequest.setInfoMsg(String.format("Count: %d", observeCancelCnt)); - handler.sentRpcResponse(rpcRequest, CONTENT.name(), null, LOG_LW2M_INFO); + private static ContentFormat convertResourceModelTypeToContentFormat(LwM2mClient client, ResourceModel.Type type) { + switch (type) { + case BOOLEAN: + case STRING: + case TIME: + case INTEGER: + case FLOAT: + return client.getDefaultContentFormat(); + case OPAQUE: + return ContentFormat.OPAQUE; + case OBJLNK: + return ContentFormat.LINK; + default: } + throw new CodecException("Invalid ResourceModel_Type for %s ContentFormat.", type); } + private static ContentFormat getContentFormat(LwM2mClient client, HasContentFormat request) { + return request.getContentFormat() != null ? request.getContentFormat() : client.getDefaultContentFormat(); + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java index 40ad57e026..ed6226fc1d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java @@ -15,34 +15,56 @@ */ package org.thingsboard.server.transport.lwm2m.server; -import org.eclipse.leshan.core.request.ContentFormat; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.eclipse.leshan.core.response.CancelObservationResponse; +import org.eclipse.leshan.core.response.DeleteResponse; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteAttributesResponse; +import org.eclipse.leshan.core.response.WriteResponse; 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; +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.TbLwM2MDiscoverRequest; +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.TbLwM2MObserveRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; +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.TbLwM2MWriteUpdateRequest; + +import java.util.Set; public interface LwM2mDownlinkMsgHandler { - void sendReadRequest(LwM2mClient client, String targetId, Long timeout); + void sendReadRequest(LwM2mClient client, TbLwM2MReadRequest request, DownlinkRequestCallback callback); + + void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback); + + void sendObserveAllRequest(LwM2mClient client, TbLwM2MObserveAllRequest request, DownlinkRequestCallback> callback); - void sendReadRequest(LwM2mClient client, String targetId, ContentFormat contentFormat, Long timeout); + void sendExecuteRequest(LwM2mClient client, TbLwM2MExecuteRequest request, DownlinkRequestCallback callback); - void sendObserveRequest(LwM2mClient client, String targetId, Long timeout); + void sendDeleteRequest(LwM2mClient client, TbLwM2MDeleteRequest request, DownlinkRequestCallback callback); - void sendObserveRequest(LwM2mClient client, String targetId, ContentFormat contentFormat, Long timeout); + void sendCancelObserveRequest(LwM2mClient client, TbLwM2MCancelObserveRequest request, DownlinkRequestCallback callback); - void sendExecuteRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback); + void sendCancelAllRequest(LwM2mClient client, TbLwM2MCancelAllRequest request, DownlinkRequestCallback callback); - void sendExecuteRequest(LwM2mClient client, String targetId, Object params, Long timeout, DownlinkRequestCallback callback); + void sendDiscoverRequest(LwM2mClient client, TbLwM2MDiscoverRequest request, DownlinkRequestCallback callback); - void sendCancelObserveRequest(LwM2mClient client, String targetId, Long timeout, DownlinkRequestCallback callback); + void sendDiscoverAllRequest(LwM2mClient client, TbLwM2MDiscoverAllRequest request, DownlinkRequestCallback> callback); - void sendCancelAllRequest(LwM2mClient client, Long timeout, DownlinkRequestCallback callback); + void sendWriteAttributesRequest(LwM2mClient client, TbLwM2MWriteAttributesRequest request, DownlinkRequestCallback callback); - void sendDiscoverRequest(LwM2mClient client, String targetId, Long timeout); + void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback); - void sendWriteAttributesRequest(LwM2mClient client, String targetId, ObjectAttributes params, Long timeout); + void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback); - void sendWriteReplaceRequest(LwM2mClient client, String targetIdVer, Object newValue, Long timeout, DownlinkRequestCallback callback); - void sendWriteUpdateRequest(LwM2mClient client, String targetIdVer, Object newValue, ContentFormat contentFormat, Long timeout, DownlinkRequestCallback callback); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mUplinkMsgHandler.java index fcccf29f1d..132bd8689d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mUplinkMsgHandler.java @@ -38,8 +38,6 @@ public interface LwM2mUplinkMsgHandler { void onSleepingDev(Registration registration); - void setCancelObservationsAll(Registration registration); - void onUpdateValueAfterReadResponse(Registration registration, String path, ReadResponse response, LwM2mClientRpcRequest rpcRequest); void onAttributeUpdate(TransportProtos.AttributeUpdateNotificationMsg msg, TransportProtos.SessionInfoProto sessionInfo); 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 5c210d2af3..9148a44e93 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,6 +39,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.TsKvProto; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import java.util.Collection; @@ -394,18 +395,18 @@ public class LwM2mClient implements Cloneable { } } - public LwM2mFwSwUpdate getFwUpdate (LwM2mClientContext clientContext) { + public LwM2mFwSwUpdate getFwUpdate(LwM2mUplinkMsgHandler handler, LwM2mClientContext clientContext) { if (this.fwUpdate == null) { var profile = clientContext.getProfile(this.getProfileId()); - this.fwUpdate = new LwM2mFwSwUpdate(this, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy()); + this.fwUpdate = new LwM2mFwSwUpdate(handler, this, OtaPackageType.FIRMWARE, profile.getClientLwM2mSettings().getFwUpdateStrategy()); } return this.fwUpdate; } - public LwM2mFwSwUpdate getSwUpdate (LwM2mClientContext clientContext) { + public LwM2mFwSwUpdate getSwUpdate (LwM2mUplinkMsgHandler handler,LwM2mClientContext clientContext) { if (this.swUpdate == null) { var profile = clientContext.getProfile(this.getProfileId()); - this.swUpdate = new LwM2mFwSwUpdate(this, OtaPackageType.SOFTWARE, profile.getClientLwM2mSettings().getSwUpdateStrategy()); + 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 index efc5aa9657..0dda5d7269 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 @@ -27,6 +27,13 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteReplaceRequest; import java.util.ArrayList; import java.util.List; @@ -112,6 +119,9 @@ public class LwM2mFwSwUpdate { @Setter private volatile boolean infoFwSwUpdate = false; private final OtaPackageType type; + + private final LwM2mUplinkMsgHandler handler; + @Getter LwM2mClient lwM2MClient; @Getter @@ -124,7 +134,8 @@ public class LwM2mFwSwUpdate { @Setter private volatile int updateStrategy; - public LwM2mFwSwUpdate(LwM2mClient lwM2MClient, OtaPackageType type, 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; @@ -189,7 +200,9 @@ public class LwM2mFwSwUpdate { int chunkSize = 0; int chunk = 0; byte[] firmwareChunk = handler.otaPackageDataCache.get(this.currentId.toString(), chunkSize, chunk); - request.sendWriteReplaceRequest(this.lwM2MClient, targetIdVer, firmwareChunk, handler.config.getTimeout(), this.rpcRequest); + + TbLwM2MWriteReplaceRequest downlink = TbLwM2MWriteReplaceRequest.builder().versionedId(targetIdVer).value(firmwareChunk).timeout(handler.config.getTimeout()).build(); + request.sendWriteReplaceRequest(lwM2MClient, downlink, new TbLwM2MWriteReplaceCallback(handler, lwM2MClient, targetIdVer)); } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL.code == this.updateStrategy) { Registration registration = this.getLwM2MClient().getRegistration(); // String api = handler.config.getHostRequests(); @@ -197,7 +210,9 @@ public class LwM2mFwSwUpdate { int port = registration.getIdentity().isSecure() ? handler.config.getSecurePort() : handler.config.getPort(); String uri = "coap://" + api + ":" + Integer.valueOf(port) + "/" + FIRMWARE_UPDATE_COAP_RECOURSE + "/" + this.currentId.toString(); log.warn("89) coapUri: [{}]", uri); - request.sendWriteReplaceRequest(this.lwM2MClient, targetIdVer, uri, handler.config.getTimeout(), this.rpcRequest); + //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)); } else if (LwM2mTransportUtil.LwM2MFirmwareUpdateStrategy.OBJ_19_BINARY.code == this.updateStrategy) { } @@ -230,7 +245,9 @@ public class LwM2mFwSwUpdate { */ public void executeFwSwWare(DefaultLwM2MUplinkMsgHandler handler, LwM2mDownlinkMsgHandler request) { this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); - request.sendExecuteRequest(this.lwM2MClient, this.pathInstallId, handler.config.getTimeout(), this.rpcRequest); + //TODO: user this.rpcRequest??? + TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(pathInstallId).timeout(handler.config.getTimeout()).build(); + request.sendExecuteRequest(lwM2MClient, downlink, new TbLwM2MExecuteRequestCallback(handler, lwM2MClient, pathInstallId)); } /** @@ -253,23 +270,21 @@ public class LwM2mFwSwUpdate { String ver3 = (String) this.lwM2MClient.getResourceValue(null, FW_3_VER_ID); // #1/#2 String fwMsg = null; - if ((this.currentVersion != 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))) { + 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, this.currentVersion); - } - else if (updateResultFw != null && updateResultFw > LwM2mTransportUtil.UpdateResultFw.UPDATE_SUCCESSFULLY.code) { + } 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, updateResultFw); } if (fwMsg != null) { handler.sendLogsToThingsboard(fwMsg, lwM2MClient.getRegistration().getId()); return false; - } - else { + } else { return true; } } @@ -415,8 +430,9 @@ public class LwM2mFwSwUpdate { this.pendingInfoRequestsStart.add(convertPathFromObjectIdToIdVer( this.pathNameId, this.lwM2MClient.getRegistration())); } - this.pendingInfoRequestsStart.forEach(pathIdVer -> { - request.sendObserveRequest(this.lwM2MClient, pathIdVer, 0, this.rpcRequest); + this.pendingInfoRequestsStart.forEach(versionedId -> { + TbLwM2MObserveRequest downlink = TbLwM2MObserveRequest.builder().versionedId(versionedId).build(); + request.sendObserveRequest(this.lwM2MClient, downlink, new TbLwM2MObserveRequestCallback(handler, lwM2MClient, versionedId)); }); } @@ -449,7 +465,7 @@ 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 (StringUtils.isNotEmpty(state) && !FAILED.name().equals(this.stateUpdate) && !state.equals(this.stateUpdate)) { + if (StringUtils.isNotEmpty(state) && !FAILED.name().equals(this.stateUpdate) && !state.equals(this.stateUpdate)) { this.stateUpdate = state; this.sendSateOnThingsBoard(handler); } 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 new file mode 100644 index 0000000000..16723aaadf --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MRequestCallback.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.transport.lwm2m.server.downlink; + +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public abstract class AbstractTbLwM2MRequestCallback implements DownlinkRequestCallback { + + protected final LwM2mUplinkMsgHandler handler; + protected final LwM2mClient client; + + protected AbstractTbLwM2MRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) { + this.handler = handler; + this.client = client; + } + + @Override + public void onValidationError(String msg) { + + } + + @Override + public void onError(Exception e) { + + } +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MTargetedDownlinkRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MTargetedDownlinkRequest.java new file mode 100644 index 0000000000..7d81308df1 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/AbstractTbLwM2MTargetedDownlinkRequest.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.downlink; + +import lombok.Getter; + +public abstract class AbstractTbLwM2MTargetedDownlinkRequest implements TbLwM2MDownlinkRequest, HasVersionedId { + + @Getter + private final String versionedId; + @Getter + private final long timeout; + + public AbstractTbLwM2MTargetedDownlinkRequest(String versionedId, long timeout) { + this.versionedId = versionedId; + this.timeout = timeout; + } + +} 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 84f1854e1d..342abf39f4 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,11 +15,30 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +public interface DownlinkRequestCallback { -public interface DownlinkRequestCallback { + void onSuccess(T response); - void onSuccess(LwM2mClient client, String msg); + void onValidationError(String msg); - void onError(LwM2mClient client, String msg, Exception e); + void onError(Exception e); + + static DownlinkRequestCallback doNothing() { + return new DownlinkRequestCallback() { + @Override + public void onSuccess(T response) { + + } + + @Override + public void onValidationError(String msg) { + + } + + @Override + public void onError(Exception e) { + + } + }; + } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.java new file mode 100644 index 0000000000..6d2ecc98ed --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasContentFormat.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.transport.lwm2m.server.downlink; + +import org.eclipse.leshan.core.request.ContentFormat; + +public interface HasContentFormat { + + ContentFormat getContentFormat(); +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.java new file mode 100644 index 0000000000..fcbb195935 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/HasVersionedId.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.downlink; + +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public interface HasVersionedId { + + String getVersionedId(); + + default String getObjectId(){ + return LwM2mTransportUtil.fromVersionedIdToObjectId(getVersionedId()); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveRequestCallback.java new file mode 100644 index 0000000000..a76f317085 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveRequestCallback.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.downlink; + +import org.thingsboard.server.transport.lwm2m.server.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.LwM2mTypeOper.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL_ALL; + +public class TbLwM2MCancelAllObserveRequestCallback extends AbstractTbLwM2MRequestCallback { + + public TbLwM2MCancelAllObserveRequestCallback(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); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java new file mode 100644 index 0000000000..b6f796aefd --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.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.downlink; + +import lombok.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.response.CancelObservationResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MCancelAllRequest implements TbLwM2MDownlinkRequest { + + @Getter + private final long timeout; + + @Builder + private TbLwM2MCancelAllRequest(long timeout) { + this.timeout = timeout; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL_ALL; + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java new file mode 100644 index 0000000000..51b016f80d --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Builder; +import org.eclipse.leshan.core.response.CancelObservationResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MCancelObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Builder + private TbLwM2MCancelObserveRequest(String versionedId, long timeout) { + super(versionedId, timeout); + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequestCallback.java new file mode 100644 index 0000000000..48d7519006 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequestCallback.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.downlink; + +import org.thingsboard.server.transport.lwm2m.server.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.LwM2mTypeOper.OBSERVE_CANCEL; + +public class TbLwM2MCancelObserveRequestCallback extends AbstractTbLwM2MRequestCallback { + + private final String versionedId; + + public TbLwM2MCancelObserveRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String versionedId) { + super(handler, client); + this.versionedId = versionedId; + } + + @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); + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java new file mode 100644 index 0000000000..63a360b822 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MDeleteRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Builder + private TbLwM2MDeleteRequest(String versionedId, long timeout) { + super(versionedId, timeout); + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.DELETE; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequestCallback.java new file mode 100644 index 0000000000..106f34a14d --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequestCallback.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.eclipse.leshan.core.response.DeleteResponse; +import org.eclipse.leshan.core.response.ExecuteResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public class TbLwM2MDeleteRequestCallback extends AbstractTbLwM2MRequestCallback { + + private final String targetId; + + public TbLwM2MDeleteRequestCallback(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. + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java new file mode 100644 index 0000000000..16f32a9ecc --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.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.transport.lwm2m.server.downlink; + +import lombok.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MDiscoverAllRequest implements TbLwM2MDownlinkRequest { + + @Getter + private final long timeout; + + @Builder + private TbLwM2MDiscoverAllRequest(long timeout) { + this.timeout = timeout; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_ALL; + } + + + +} 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 new file mode 100644 index 0000000000..fa0e3cf761 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverCallback.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.eclipse.leshan.core.response.DeleteResponse; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public class TbLwM2MDiscoverCallback extends AbstractTbLwM2MRequestCallback { + + private final String targetId; + + 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. + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java new file mode 100644 index 0000000000..6b0dcc4994 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Builder; +import org.eclipse.leshan.core.response.DiscoverResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MDiscoverRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Builder + private TbLwM2MDiscoverRequest(String versionedId, long timeout) { + super(versionedId, timeout); + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java new file mode 100644 index 0000000000..a069bb4ddc --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.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.downlink; + +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public interface TbLwM2MDownlinkRequest { + + LwM2mTransportUtil.LwM2mTypeOper getType(); + + long getTimeout(); + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java new file mode 100644 index 0000000000..32d1be665c --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.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.downlink; + +import lombok.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MExecuteRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Getter + private final Object params; + + @Builder + private TbLwM2MExecuteRequest(String versionedId, long timeout, Object params) { + super(versionedId, timeout); + this.params = params; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequestCallback.java new file mode 100644 index 0000000000..4a2ca7f1fb --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequestCallback.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.eclipse.leshan.core.response.ExecuteResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public class TbLwM2MExecuteRequestCallback extends AbstractTbLwM2MRequestCallback { + + private final String targetId; + + public TbLwM2MExecuteRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + super(handler, client); + this.targetId = targetId; + } + + @Override + public void onSuccess(ExecuteResponse 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/TbLwM2MObserveAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java new file mode 100644 index 0000000000..b12b4469d6 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.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.downlink; + +import lombok.Builder; +import lombok.Getter; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +import java.util.Set; + +public class TbLwM2MObserveAllRequest implements TbLwM2MDownlinkRequest> { + + @Getter + private final long timeout; + + @Builder + private TbLwM2MObserveAllRequest(long timeout) { + this.timeout = timeout; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_READ_ALL; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java new file mode 100644 index 0000000000..1239725be2 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.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.downlink; + +import lombok.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { + + @Getter + private final ContentFormat contentFormat; + + @Builder + private TbLwM2MObserveRequest(String versionedId, long timeout, ContentFormat contentFormat) { + super(versionedId, timeout); + this.contentFormat = contentFormat; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequestCallback.java new file mode 100644 index 0000000000..65f04cd920 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequestCallback.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public class TbLwM2MObserveRequestCallback extends AbstractTbLwM2MRequestCallback { + + private final String targetId; + + public TbLwM2MObserveRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + super(handler, client); + this.targetId = targetId; + } + + @Override + public void onSuccess(ObserveResponse response) { + //TODO: handle the response (at least log to telemetry) + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java new file mode 100644 index 0000000000..5d88a20ae4 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { + + @Getter + private final ContentFormat contentFormat; + + @Builder + private TbLwM2MReadRequest(String versionedId, long timeout, ContentFormat contentFormat) { + super(versionedId, timeout); + this.contentFormat = contentFormat; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.READ; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequestCallback.java new file mode 100644 index 0000000000..345c585b1f --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequestCallback.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.eclipse.leshan.core.response.ReadResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public class TbLwM2MReadRequestCallback extends AbstractTbLwM2MRequestCallback { + + private final String targetId; + + public TbLwM2MReadRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + super(handler, client); + this.targetId = targetId; + } + + @Override + public void onSuccess(ReadResponse response) { + //TODO: separate callback wrapper for the RPC calls. + handler.onUpdateValueAfterReadResponse(client.getRegistration(), targetId, response, null); + } + +} 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 new file mode 100644 index 0000000000..4bda629256 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.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.downlink; + +import org.eclipse.leshan.core.response.ReadResponse; +import org.eclipse.leshan.core.response.WriteAttributesResponse; +import org.eclipse.leshan.core.response.WriteResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public class TbLwM2MWriteAttributesCallback extends AbstractTbLwM2MRequestCallback { + + private final String targetId; + + public TbLwM2MWriteAttributesCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + super(handler, client); + this.targetId = targetId; + } + + @Override + public void onSuccess(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/TbLwM2MWriteAttributesRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java new file mode 100644 index 0000000000..dd1f9311d9 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.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.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.request.ContentFormat; +import org.eclipse.leshan.core.response.ObserveResponse; +import org.eclipse.leshan.core.response.WriteAttributesResponse; +import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MWriteAttributesRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Getter + private final ObjectAttributes attributes; + + @Builder + private TbLwM2MWriteAttributesRequest(String versionedId, long timeout, ObjectAttributes attributes) { + super(versionedId, timeout); + this.attributes = attributes; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; + } + + + +} 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/TbLwM2MWriteReplaceCallback.java new file mode 100644 index 0000000000..b15632da19 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceCallback.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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 org.eclipse.leshan.core.response.WriteAttributesResponse; +import org.eclipse.leshan.core.response.WriteResponse; +import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; + +public class TbLwM2MWriteReplaceCallback extends AbstractTbLwM2MRequestCallback { + + private final String targetId; + + public TbLwM2MWriteReplaceCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + super(handler, client); + this.targetId = targetId; + } + + @Override + public void onSuccess(WriteResponse 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/TbLwM2MWriteReplaceRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.java new file mode 100644 index 0000000000..71c1080655 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteReplaceRequest.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.downlink; + +import lombok.Builder; +import lombok.Getter; +import org.eclipse.leshan.core.response.WriteAttributesResponse; +import org.eclipse.leshan.core.response.WriteResponse; +import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; + +public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Getter + private final Object value; + + @Builder + private TbLwM2MWriteReplaceRequest(String versionedId, long timeout, Object value) { + super(versionedId, timeout); + this.value = value; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; + } + + + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java new file mode 100644 index 0000000000..071d9fc889 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.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.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.LwM2mTransportUtil; + +public class TbLwM2MWriteUpdateRequest extends AbstractTbLwM2MTargetedDownlinkRequest { + + @Getter + private final Object value; + @Getter + private final ContentFormat objectContentFormat; + + @Builder + private TbLwM2MWriteUpdateRequest(String versionedId, long timeout, Object value, ContentFormat objectContentFormat) { + super(versionedId, timeout); + this.value = value; + this.objectContentFormat = objectContentFormat; + } + + @Override + public LwM2mTransportUtil.LwM2mTypeOper getType() { + return LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE; + } + + + +} From 26b8a7ff3aae22f24be17a9622f3e83b4451c4b5 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 11 Jun 2021 12:35:57 +0300 Subject: [PATCH 041/231] Rename of the callbacks --- .../server/DefaultLwM2MUplinkMsgHandler.java | 18 +++++++----------- .../lwm2m/server/client/LwM2mFwSwUpdate.java | 12 +++++------- .../DefaultLwM2mDownlinkMsgHandler.java | 17 ++--------------- .../LwM2mDownlinkMsgHandler.java | 16 +--------------- ...va => TbLwM2MCancelAllObserveCallback.java} | 5 ++--- .../downlink/TbLwM2MCancelAllRequest.java | 1 - ....java => TbLwM2MCancelObserveCallback.java} | 4 ++-- .../downlink/TbLwM2MCancelObserveRequest.java | 2 -- ...allback.java => TbLwM2MDeleteCallback.java} | 5 ++--- .../server/downlink/TbLwM2MDeleteRequest.java | 1 - .../downlink/TbLwM2MDiscoverAllRequest.java | 1 - .../downlink/TbLwM2MDiscoverCallback.java | 1 - .../downlink/TbLwM2MDiscoverRequest.java | 1 - ...llback.java => TbLwM2MExecuteCallback.java} | 5 ++--- ...llback.java => TbLwM2MObserveCallback.java} | 5 ++--- .../server/downlink/TbLwM2MObserveRequest.java | 1 - ...tCallback.java => TbLwM2MReadCallback.java} | 4 ++-- .../TbLwM2MWriteAttributesCallback.java | 2 -- .../TbLwM2MWriteAttributesRequest.java | 2 -- .../downlink/TbLwM2MWriteReplaceCallback.java | 1 - .../downlink/TbLwM2MWriteReplaceRequest.java | 2 -- 21 files changed, 27 insertions(+), 79 deletions(-) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{ => downlink}/DefaultLwM2mDownlinkMsgHandler.java (97%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{ => downlink}/LwM2mDownlinkMsgHandler.java (68%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/{TbLwM2MCancelAllObserveRequestCallback.java => TbLwM2MCancelAllObserveCallback.java} (80%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/{TbLwM2MCancelObserveRequestCallback.java => TbLwM2MCancelObserveCallback.java} (86%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/{TbLwM2MDeleteRequestCallback.java => TbLwM2MDeleteCallback.java} (81%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/{TbLwM2MExecuteRequestCallback.java => TbLwM2MExecuteCallback.java} (81%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/{TbLwM2MObserveRequestCallback.java => TbLwM2MObserveCallback.java} (81%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/{TbLwM2MReadRequestCallback.java => TbLwM2MReadCallback.java} (85%) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java index e6c14b0ba0..55129e59c9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java @@ -29,7 +29,6 @@ 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.CancelObservationRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.server.registration.Registration; @@ -67,18 +66,15 @@ 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.TbLwM2MCancelAllObserveRequestCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveCallback; 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.TbLwM2MObserveAllRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequestCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadCallback; 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; @@ -659,12 +655,12 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private void sendReadRequest(LwM2mClient lwM2MClient, String versionedId) { TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, request, new TbLwM2MReadRequestCallback(this, lwM2MClient, versionedId)); + defaultLwM2MDownlinkMsgHandler.sendReadRequest(lwM2MClient, request, new TbLwM2MReadCallback(this, lwM2MClient, versionedId)); } private void sendObserveRequest(LwM2mClient lwM2MClient, String versionedId) { TbLwM2MObserveRequest request = TbLwM2MObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendObserveRequest(lwM2MClient, request, new TbLwM2MObserveRequestCallback(this, lwM2MClient, versionedId)); + defaultLwM2MDownlinkMsgHandler.sendObserveRequest(lwM2MClient, request, new TbLwM2MObserveCallback(this, lwM2MClient, versionedId)); } private void sendWriteAttributesRequest(LwM2mClient lwM2MClient, String targetId, ObjectAttributes params) { @@ -674,7 +670,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private void sendCancelObserveRequest(String versionedId, LwM2mClient client) { TbLwM2MCancelObserveRequest request = TbLwM2MCancelObserveRequest.builder().versionedId(versionedId).timeout(this.config.getTimeout()).build(); - defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveRequestCallback(this, client, versionedId)); + defaultLwM2MDownlinkMsgHandler.sendCancelObserveRequest(client, request, new TbLwM2MCancelObserveCallback(this, client, versionedId)); } /** 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 0dda5d7269..7a7ca21a0f 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 @@ -19,19 +19,18 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.eclipse.leshan.core.request.ContentFormat; 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.DefaultLwM2MUplinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.LwM2mDownlinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.downlink.LwM2mDownlinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MExecuteRequestCallback; +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.TbLwM2MObserveRequestCallback; +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.TbLwM2MWriteReplaceRequest; @@ -67,7 +66,6 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.L 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.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.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; @@ -247,7 +245,7 @@ public class LwM2mFwSwUpdate { this.sendLogs(handler, EXECUTE.name(), LOG_LW2M_INFO, null); //TODO: user this.rpcRequest??? TbLwM2MExecuteRequest downlink = TbLwM2MExecuteRequest.builder().versionedId(pathInstallId).timeout(handler.config.getTimeout()).build(); - request.sendExecuteRequest(lwM2MClient, downlink, new TbLwM2MExecuteRequestCallback(handler, lwM2MClient, pathInstallId)); + request.sendExecuteRequest(lwM2MClient, downlink, new TbLwM2MExecuteCallback(handler, lwM2MClient, pathInstallId)); } /** @@ -432,7 +430,7 @@ public class LwM2mFwSwUpdate { } this.pendingInfoRequestsStart.forEach(versionedId -> { TbLwM2MObserveRequest downlink = TbLwM2MObserveRequest.builder().versionedId(versionedId).build(); - request.sendObserveRequest(this.lwM2MClient, downlink, new TbLwM2MObserveRequestCallback(handler, lwM2MClient, versionedId)); + request.sendObserveRequest(this.lwM2MClient, downlink, new TbLwM2MObserveCallback(handler, lwM2MClient, versionedId)); }); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java similarity index 97% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.java index b1b52ec3ed..189d359762 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/DefaultLwM2mDownlinkMsgHandler.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.downlink; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -50,23 +50,10 @@ import org.springframework.stereotype.Service; 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; +import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; 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.server.downlink.DownlinkRequestCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.HasContentFormat; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelAllRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; -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.TbLwM2MDiscoverRequest; -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.TbLwM2MObserveRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; -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.TbLwM2MWriteUpdateRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java similarity index 68% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java index ed6226fc1d..473205eaa1 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mDownlinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/LwM2mDownlinkMsgHandler.java @@ -13,9 +13,8 @@ * 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.downlink; -import org.eclipse.leshan.core.response.CancelObservationResponse; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.eclipse.leshan.core.response.ExecuteResponse; @@ -24,19 +23,6 @@ import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; 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; -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.TbLwM2MDiscoverRequest; -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.TbLwM2MObserveRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; -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.TbLwM2MWriteUpdateRequest; import java.util.Set; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java similarity index 80% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveRequestCallback.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java index a76f317085..87e219ef96 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllObserveCallback.java @@ -19,12 +19,11 @@ import org.thingsboard.server.transport.lwm2m.server.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.LwM2mTypeOper.OBSERVE_CANCEL; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL_ALL; -public class TbLwM2MCancelAllObserveRequestCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback { - public TbLwM2MCancelAllObserveRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) { + public TbLwM2MCancelAllObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client) { super(handler, client); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java index b6f796aefd..02ff01f973 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.eclipse.leshan.core.response.CancelObservationResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; public class TbLwM2MCancelAllRequest implements TbLwM2MDownlinkRequest { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java similarity index 86% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequestCallback.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java index 48d7519006..8d9c13a428 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveCallback.java @@ -21,11 +21,11 @@ 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.LwM2mTypeOper.OBSERVE_CANCEL; -public class TbLwM2MCancelObserveRequestCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback { private final String versionedId; - public TbLwM2MCancelObserveRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String versionedId) { + public TbLwM2MCancelObserveCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String versionedId) { super(handler, client); this.versionedId = versionedId; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java index 51b016f80d..bf1ba36216 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java @@ -16,8 +16,6 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; -import org.eclipse.leshan.core.response.CancelObservationResponse; -import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; public class TbLwM2MCancelObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java similarity index 81% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequestCallback.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java index 106f34a14d..f6b0bfac81 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteCallback.java @@ -16,15 +16,14 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.response.DeleteResponse; -import org.eclipse.leshan.core.response.ExecuteResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MDeleteRequestCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MDeleteCallback extends AbstractTbLwM2MRequestCallback { private final String targetId; - public TbLwM2MDeleteRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + public TbLwM2MDeleteCallback(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/downlink/TbLwM2MDeleteRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java index 63a360b822..62532c1726 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; -import lombok.Getter; import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java index 16f32a9ecc..9400e3bfba 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.eclipse.leshan.core.response.DiscoverResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; public class TbLwM2MDiscoverAllRequest implements TbLwM2MDownlinkRequest { 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 fa0e3cf761..dc3269b5a8 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,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java index 6b0dcc4994..6b8319bc25 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java @@ -17,7 +17,6 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import org.eclipse.leshan.core.response.DiscoverResponse; -import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; public class TbLwM2MDiscoverRequest extends AbstractTbLwM2MTargetedDownlinkRequest { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java similarity index 81% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequestCallback.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java index 4a2ca7f1fb..f43a648cc3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteCallback.java @@ -16,15 +16,14 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.response.ExecuteResponse; -import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MExecuteRequestCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MExecuteCallback extends AbstractTbLwM2MRequestCallback { private final String targetId; - public TbLwM2MExecuteRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + public TbLwM2MExecuteCallback(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/downlink/TbLwM2MObserveRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java similarity index 81% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequestCallback.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java index 65f04cd920..d57a348186 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveCallback.java @@ -16,15 +16,14 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MObserveRequestCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MObserveCallback extends AbstractTbLwM2MRequestCallback { private final String targetId; - public TbLwM2MObserveRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + public TbLwM2MObserveCallback(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/downlink/TbLwM2MObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java index 1239725be2..60fb9468f3 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java @@ -19,7 +19,6 @@ import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.ObserveResponse; -import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequestCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java similarity index 85% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequestCallback.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java index 345c585b1f..f142d53c09 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequestCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadCallback.java @@ -19,11 +19,11 @@ import org.eclipse.leshan.core.response.ReadResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; -public class TbLwM2MReadRequestCallback extends AbstractTbLwM2MRequestCallback { +public class TbLwM2MReadCallback extends AbstractTbLwM2MRequestCallback { private final String targetId; - public TbLwM2MReadRequestCallback(LwM2mUplinkMsgHandler handler, LwM2mClient client, String targetId) { + public TbLwM2MReadCallback(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/downlink/TbLwM2MWriteAttributesCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesCallback.java index 4bda629256..89802c1bf1 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 @@ -15,9 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteAttributesResponse; -import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java index dd1f9311d9..baa7401742 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java @@ -17,8 +17,6 @@ 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.ObserveResponse; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; 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/TbLwM2MWriteReplaceCallback.java index b15632da19..ecfa9241de 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/TbLwM2MWriteReplaceCallback.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; 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 71c1080655..488942f7de 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,9 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.eclipse.leshan.core.response.WriteResponse; -import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkRequest { From bafbf7b239386a86077f32abc67e33befc101a4c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 11 Jun 2021 14:34:43 +0300 Subject: [PATCH 042/231] added rpc ttl --- .../service/ttl/rpc/RpcCleanUpService.java | 83 +++++++++++++++++++ .../src/main/resources/thingsboard.yml | 3 + .../DefaultTenantProfileConfiguration.java | 1 + .../thingsboard/server/dao/rpc/RpcDao.java | 3 + .../server/dao/sql/rpc/JpaRpcDao.java | 6 ++ .../server/dao/sql/rpc/RpcRepository.java | 6 ++ ...enant-profile-configuration.component.html | 12 +++ ...-tenant-profile-configuration.component.ts | 3 +- ui-ngx/src/app/shared/models/tenant.model.ts | 6 +- .../assets/locale/locale.constant-en_US.json | 3 + 10 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java new file mode 100644 index 0000000000..c0985eb4c1 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/ttl/rpc/RpcCleanUpService.java @@ -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. + */ +package org.thingsboard.server.service.ttl.rpc; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +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.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.dao.rpc.RpcDao; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.util.Date; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@TbCoreComponent +@Service +@Slf4j +@RequiredArgsConstructor +public class RpcCleanUpService { + @Value("${sql.ttl.rpc.enabled}") + private boolean ttlTaskExecutionEnabled; + + private final TenantDao tenantDao; + private final PartitionService partitionService; + private final TbTenantProfileCache tenantProfileCache; + private final RpcDao rpcDao; + + @Scheduled(initialDelayString = "#{T(org.apache.commons.lang3.RandomUtils).nextLong(0, ${sql.ttl.rpc.checking_interval})}", fixedDelayString = "${sql.ttl.rpc.checking_interval}") + public void cleanUp() { + if (ttlTaskExecutionEnabled) { + PageLink tenantsBatchRequest = new PageLink(10_000, 0); + PageData tenantsIds; + do { + tenantsIds = tenantDao.findTenantsIds(tenantsBatchRequest); + for (TenantId tenantId : tenantsIds.getData()) { + if (!partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { + continue; + } + + Optional tenantProfileConfiguration = tenantProfileCache.get(tenantId).getProfileConfiguration(); + if (tenantProfileConfiguration.isEmpty() || tenantProfileConfiguration.get().getRpcTtlDays() == 0) { + continue; + } + + long ttl = TimeUnit.DAYS.toMillis(tenantProfileConfiguration.get().getRpcTtlDays()); + long expirationTime = System.currentTimeMillis() - ttl; + + long totalRemoved = rpcDao.deleteOutdatedRpcByTenantId(tenantId, expirationTime); + + if (totalRemoved > 0) { + log.info("Removed {} outdated rpc(s) for tenant {} older than {}", totalRemoved, tenantId, new Date(expirationTime)); + } + } + + tenantsBatchRequest = tenantsBatchRequest.nextPageLink(); + } while (tenantsIds.hasNext()); + } + } + +} diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 54721ba2fa..7fd0d47184 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -276,6 +276,9 @@ sql: alarms: checking_interval: "${SQL_ALARMS_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours removal_batch_size: "${SQL_ALARMS_TTL_REMOVAL_BATCH_SIZE:3000}" # To delete outdated alarms not all at once but in batches + rpc: + enabled: "${SQL_TTL_RPC_ENABLED:true}" + checking_interval: "${SQL_RPC_TTL_CHECKING_INTERVAL:7200000}" # Number of milliseconds. The current value corresponds to two hours # Actor system parameters actors: 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 8cdccfe8bd..ce10f95055 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 @@ -56,6 +56,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private int defaultStorageTtlDays; private int alarmsTtlDays; + private int rpcTtlDays; private double warnThreshold; diff --git a/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java index 753d1043f3..a3aa32720a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.rpc; import org.thingsboard.server.common.data.id.DeviceId; +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.rpc.Rpc; @@ -24,4 +25,6 @@ import org.thingsboard.server.dao.Dao; public interface RpcDao extends Dao { PageData findAllByDeviceId(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink); + + Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java index 1a538c9d9d..22fc3396f7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java @@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.DeviceId; +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.rpc.Rpc; @@ -53,4 +54,9 @@ public class JpaRpcDao extends JpaAbstractDao implements RpcDao public PageData findAllByDeviceId(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { return DaoUtil.toPageData(rpcRepository.findAllByDeviceIdAndStatus(deviceId.getId(), rpcStatus, DaoUtil.toPageable(pageLink))); } + + @Override + public Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime) { + return rpcRepository.deleteOutdatedRpcByTenantId(tenantId.getId(), expirationTime); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java index bc04aa7e71..565d87e627 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java @@ -17,7 +17,9 @@ package org.thingsboard.server.dao.sql.rpc; 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.common.data.rpc.RpcStatus; import org.thingsboard.server.dao.model.sql.RpcEntity; @@ -25,4 +27,8 @@ import java.util.UUID; public interface RpcRepository extends CrudRepository { Page findAllByDeviceIdAndStatus(UUID deviceId, RpcStatus status, Pageable pageable); + + @Query(value = "WITH deleted AS (DELETE FROM rpc WHERE (tenant_id = :tenantId AND created_time < :expirationTime) IS TRUE RETURNING *) SELECT count(*) FROM deleted", + nativeQuery = true) + Long deleteOutdatedRpcByTenantId(@Param("tenantId") UUID tenantId, @Param("expirationTime") Long expirationTime); } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 4dd248a10c..66cb503bd3 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -196,6 +196,18 @@ {{ 'tenant-profile.alarms-ttl-days-days-range' | translate}} + + tenant-profile.rpc-ttl-days + + + {{ 'tenant-profile.rpc-ttl-days-required' | translate}} + + + {{ 'tenant-profile.rpc-ttl-days-days-range' | translate}} + + tenant-profile.max-rule-node-executions-per-message { this.updateModel(); diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 4ce7acb5c5..5a3683f212 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -51,6 +51,8 @@ export interface DefaultTenantProfileConfiguration { maxSms: number; defaultStorageTtlDays: number; + alarmsTtlDays: number; + rpcTtlDays: number; } export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; @@ -81,7 +83,9 @@ export function createTenantProfileConfiguration(type: TenantProfileType): Tenan maxRuleNodeExecutionsPerMessage: 0, maxEmails: 0, maxSms: 0, - defaultStorageTtlDays: 0 + defaultStorageTtlDays: 0, + alarmsTtlDays: 0, + rpcTtlDays: 0 }; configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; break; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index abe4cba802..7eb244ff4d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2570,6 +2570,9 @@ "alarms-ttl-days": "Alarms TTL days (0 - unlimited)", "alarms-ttl-days-required": "Alarms TTL days required", "alarms-ttl-days-days-range": "Alarms TTL days can't be negative", + "rpc-ttl-days": "RPC TTL days (0 - unlimited)", + "rpc-ttl-days-required": "RPC TTL days required", + "rpc-ttl-days-days-range": "RPC TTL days can't be negative", "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", "max-rule-node-executions-per-message-range": "Maximum number of rule node executions per message can't be negative", From 6b546a459ef29e7b0fcf8f0013e7f29865c49d73 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 14 Jun 2021 10:10:43 +0300 Subject: [PATCH 043/231] added RPC message types --- .../server/service/rpc/TbRpcService.java | 17 ++++++++++++----- .../server/common/data/DataConstants.java | 7 +++++++ .../rule/engine/filter/TbMsgTypeSwitchNode.java | 16 ++++++++++++++-- .../src/app/shared/models/rule-node.models.ts | 16 ++++++++++++++-- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java index d85223c43a..d410f12b1f 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; @@ -27,9 +27,11 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.action.RuleEngineEntityActionService; +import org.thingsboard.server.service.queue.TbClusterService; @TbCoreComponent @Service @@ -37,11 +39,11 @@ import org.thingsboard.server.service.action.RuleEngineEntityActionService; @Slf4j public class TbRpcService { private final RpcService rpcService; - private final RuleEngineEntityActionService ruleEngineEntityActionService; + private final TbClusterService tbClusterService; public void save(TenantId tenantId, Rpc rpc) { Rpc saved = rpcService.save(tenantId, rpc); - ruleEngineEntityActionService.pushEntityActionToRuleEngine(saved.getId(), saved, tenantId, null, rpc.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null); + pushRpcMsgToRuleEngine(tenantId, saved); } public void save(TenantId tenantId, RpcId rpcId, RpcStatus newStatus, JsonNode response) { @@ -52,12 +54,17 @@ public class TbRpcService { foundRpc.setResponse(response); } Rpc saved = rpcService.save(tenantId, foundRpc); - ruleEngineEntityActionService.pushEntityActionToRuleEngine(saved.getId(), saved, tenantId, null, ActionType.UPDATED, null); + pushRpcMsgToRuleEngine(tenantId, saved); } else { log.warn("[{}] Failed to update RPC status because RPC was already deleted", rpcId); } } + private void pushRpcMsgToRuleEngine(TenantId tenantId, Rpc rpc) { + TbMsg msg = TbMsg.newMsg("RPC_" + rpc.getStatus().name(), rpc.getDeviceId(), TbMsgMetaData.EMPTY, JacksonUtil.toString(rpc)); + tbClusterService.pushMsgToRuleEngine(tenantId, rpc.getId(), msg, null); + } + public Rpc findRpcById(TenantId tenantId, RpcId rpcId) { return rpcService.findById(tenantId, rpcId); } 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 002cbbb733..de97bf324c 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 @@ -76,6 +76,13 @@ public class DataConstants { public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; + public static final String RPC_QUEUED = "RPC_QUEUED"; + public static final String RPC_SENT = "RPC_SENT"; + public static final String RPC_DELIVERED = "RPC_DELIVERED"; + public static final String RPC_SUCCESSFUL = "RPC_SUCCESSFUL"; + public static final String RPC_TIMEOUT = "RPC_TIMEOUT"; + public static final String RPC_FAILED = "RPC_FAILED"; + public static final String DEFAULT_SECRET_KEY = ""; public static final String SECRET_KEY_FIELD_NAME = "secretKey"; public static final String DURATION_MS_FIELD_NAME = "durationMs"; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 7969836e00..235bdcfa44 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -33,8 +33,8 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; type = ComponentType.FILTER, name = "message type switch", configClazz = EmptyNodeConfiguration.class, - relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event", - "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned", + relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "RPC Queued", "RPC Sent", "RPC Delivered", "RPC Successful", "RPC Timeout", "RPC Failed", + "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned", "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant", "Timeseries Updated", "Timeseries Deleted"}, nodeDescription = "Route incoming messages by Message Type", @@ -95,6 +95,18 @@ public class TbMsgTypeSwitchNode implements TbNode { relationType = "Timeseries Updated"; } else if (msg.getType().equals(DataConstants.TIMESERIES_DELETED)) { relationType = "Timeseries Deleted"; + } else if (msg.getType().equals(DataConstants.RPC_QUEUED)) { + relationType = "RPC Queued"; + } else if (msg.getType().equals(DataConstants.RPC_SENT)) { + relationType = "RPC Sent"; + } else if (msg.getType().equals(DataConstants.RPC_DELIVERED)) { + relationType = "RPC Delivered"; + } else if (msg.getType().equals(DataConstants.RPC_SUCCESSFUL)) { + relationType = "RPC Successful"; + } else if (msg.getType().equals(DataConstants.RPC_TIMEOUT)) { + relationType = "RPC Timeout"; + } else if (msg.getType().equals(DataConstants.RPC_FAILED)) { + relationType = "RPC Failed"; } else { relationType = "Other"; } diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index 73ff97b061..49c3f18bf8 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -352,7 +352,13 @@ export enum MessageType { ATTRIBUTES_UPDATED = 'ATTRIBUTES_UPDATED', ATTRIBUTES_DELETED = 'ATTRIBUTES_DELETED', TIMESERIES_UPDATED = 'TIMESERIES_UPDATED', - TIMESERIES_DELETED = 'TIMESERIES_DELETED' + TIMESERIES_DELETED = 'TIMESERIES_DELETED', + RPC_QUEUED = 'RPC_QUEUED', + RPC_SENT = 'RPC_SENT', + RPC_DELIVERED = 'RPC_SENT', + RPC_SUCCESSFUL = 'RPC_DELIVERED', + RPC_TIMEOUT = 'RPC_TIMEOUT', + RPC_FAILED = 'RPC_FAILED' } export const messageTypeNames = new Map( @@ -373,7 +379,13 @@ export const messageTypeNames = new Map( [MessageType.ATTRIBUTES_UPDATED, 'Attributes Updated'], [MessageType.ATTRIBUTES_DELETED, 'Attributes Deleted'], [MessageType.TIMESERIES_UPDATED, 'Timeseries Updated'], - [MessageType.TIMESERIES_DELETED, 'Timeseries Deleted'] + [MessageType.TIMESERIES_DELETED, 'Timeseries Deleted'], + [MessageType.RPC_QUEUED, 'RPC Queued'], + [MessageType.RPC_SENT, 'RPC Sent'], + [MessageType.RPC_DELIVERED, 'RPC Delivered'], + [MessageType.RPC_SUCCESSFUL, 'RPC Successful'], + [MessageType.RPC_TIMEOUT, 'RPC Timeout'], + [MessageType.RPC_FAILED, 'RPC Failed'] ] ); From 79c00d75c10808d650e32d5b4afde337fb90442b Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 14 Jun 2021 11:58:20 +0300 Subject: [PATCH 044/231] WIP: Refactoring of RPC --- .../secure/LwM2MBootstrapSecurityStore.java | 2 +- .../server/DefaultLwM2mTransportService.java | 1 + .../lwm2m/server/LwM2mOperationType.java | 78 +++++++ .../lwm2m/server/LwM2mServerListener.java | 1 + .../lwm2m/server/LwM2mSessionMsgListener.java | 19 +- .../server/LwM2mTransportCoapResource.java | 2 + .../lwm2m/server/LwM2mTransportUtil.java | 71 +------ .../lwm2m/server/client/LwM2mClient.java | 4 +- .../lwm2m/server/client/LwM2mFwSwUpdate.java | 16 +- .../AbstractTbLwM2MRequestCallback.java | 2 +- .../DefaultLwM2mDownlinkMsgHandler.java | 2 +- .../TbLwM2MCancelAllObserveCallback.java | 4 +- .../downlink/TbLwM2MCancelAllRequest.java | 6 +- .../TbLwM2MCancelObserveCallback.java | 4 +- .../downlink/TbLwM2MCancelObserveRequest.java | 6 +- .../downlink/TbLwM2MDeleteCallback.java | 2 +- .../server/downlink/TbLwM2MDeleteRequest.java | 6 +- .../downlink/TbLwM2MDiscoverAllRequest.java | 6 +- .../downlink/TbLwM2MDiscoverCallback.java | 2 +- .../downlink/TbLwM2MDiscoverRequest.java | 6 +- .../downlink/TbLwM2MDownlinkRequest.java | 4 +- .../downlink/TbLwM2MExecuteCallback.java | 2 +- .../downlink/TbLwM2MExecuteRequest.java | 6 +- .../downlink/TbLwM2MObserveAllRequest.java | 6 +- .../downlink/TbLwM2MObserveCallback.java | 2 +- .../downlink/TbLwM2MObserveRequest.java | 6 +- .../server/downlink/TbLwM2MReadCallback.java | 2 +- .../server/downlink/TbLwM2MReadRequest.java | 6 +- .../TbLwM2MWriteAttributesCallback.java | 2 +- .../TbLwM2MWriteAttributesRequest.java | 6 +- .../downlink/TbLwM2MWriteReplaceCallback.java | 2 +- .../downlink/TbLwM2MWriteReplaceRequest.java | 6 +- .../downlink/TbLwM2MWriteUpdateRequest.java | 6 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 195 ++++++++++++++++++ .../lwm2m/server/rpc/IdOrKeyRequest.java | 26 +++ .../server/rpc/LwM2MRpcRequestHandler.java | 29 +++ .../LwM2mClientRpcRequest.java | 53 ++--- .../DefaultLwM2MUplinkMsgHandler.java | 135 +++--------- .../{ => uplink}/LwM2mUplinkMsgHandler.java | 16 +- 39 files changed, 469 insertions(+), 281 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHandler.java rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{client => rpc}/LwM2mClientRpcRequest.java (88%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{ => uplink}/DefaultLwM2MUplinkMsgHandler.java (91%) rename common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/{ => uplink}/LwM2mUplinkMsgHandler.java (83%) 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 e9b0edccf3..c23cc103df 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 @@ -161,7 +161,7 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore { LwM2MServerBootstrap profileLwm2mServer = JacksonUtil.fromString(JacksonUtil.toString(bootstrapObject.getLwm2mServer()), LwM2MServerBootstrap.class); UUID sessionUUiD = UUID.randomUUID(); TransportProtos.SessionInfoProto sessionInfo = helper.getValidateSessionInfo(store.getMsg(), sessionUUiD.getMostSignificantBits(), sessionUUiD.getLeastSignificantBits()); - context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, sessionInfo)); + context.getTransportService().registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(null, 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); 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 c6711c31d3..97ab82f802 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 @@ -35,6 +35,7 @@ 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.server.store.TbSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; 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 new file mode 100644 index 0000000000..694d7b96c7 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mOperationType.java @@ -0,0 +1,78 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +/** + * 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"), + /** + * 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 + */ + /** + * 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"), + + // only for RPC + FW_UPDATE(12, "FirmwareUpdate"); +// FW_READ_INFO(12, "FirmwareReadInfo"), + +// SW_READ_INFO(15, "SoftwareReadInfo"), +// SW_UPDATE(16, "SoftwareUpdate"), +// SW_UNINSTALL(18, "SoftwareUninstall"); + + public int code; + public String type; + + LwM2mOperationType(int code, String type) { + this.code = code; + this.type = type; + } + + public static LwM2mOperationType fromType(String type) { + for (LwM2mOperationType to : LwM2mOperationType.values()) { + if (to.type.equals(type)) { + return to; + } + } + 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 594148022c..97649603a6 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 @@ -23,6 +23,7 @@ import org.eclipse.leshan.server.queue.PresenceListener; import org.eclipse.leshan.server.registration.Registration; import org.eclipse.leshan.server.registration.RegistrationListener; import org.eclipse.leshan.server.registration.RegistrationUpdate; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Collection; 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 39a2194966..7448f9c71d 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 @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import org.thingsboard.server.common.data.Device; @@ -30,19 +31,19 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotifica import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportUpdateCredentialsProto; +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; import java.util.UUID; @Slf4j +@RequiredArgsConstructor public class LwM2mSessionMsgListener implements GenericFutureListener>, SessionMsgListener { - private DefaultLwM2MUplinkMsgHandler handler; - private TransportProtos.SessionInfoProto sessionInfo; - - public LwM2mSessionMsgListener(DefaultLwM2MUplinkMsgHandler handler, TransportProtos.SessionInfoProto sessionInfo) { - this.handler = handler; - this.sessionInfo = sessionInfo; - } + private final LwM2mUplinkMsgHandler handler; + private final LwM2MRpcRequestHandler rpcHandler; + private final TransportProtos.SessionInfoProto sessionInfo; @Override public void onGetAttributesResponse(GetAttributeResponseMsg getAttributesResponse) { @@ -76,12 +77,12 @@ public class LwM2mSessionMsgListener implements GenericFutureListener implements DownlinkRequestCallback { 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 189d359762..141fa61dac 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 @@ -53,7 +53,7 @@ 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.client.LwM2mClientContext; -import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientRpcRequest; +import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2mClientRpcRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; 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 87e219ef96..6d58853164 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,11 +15,11 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +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.LwM2mTypeOper.OBSERVE_CANCEL_ALL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL_ALL; public class TbLwM2MCancelAllObserveCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java index 02ff01f973..f4e02e6222 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelAllRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MCancelAllRequest implements TbLwM2MDownlinkRequest { @@ -30,8 +30,8 @@ public class TbLwM2MCancelAllRequest implements TbLwM2MDownlinkRequest } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL_ALL; + public LwM2mOperationType getType() { + return LwM2mOperationType.OBSERVE_CANCEL_ALL; } } 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 8d9c13a428..d0fc5fdd6a 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,11 +15,11 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +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.LwM2mTypeOper.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL; public class TbLwM2MCancelObserveCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java index bf1ba36216..cb4d056578 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MCancelObserveRequest.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MCancelObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -26,8 +26,8 @@ public class TbLwM2MCancelObserveRequest extends AbstractTbLwM2MTargetedDownlink } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.OBSERVE_CANCEL; + public LwM2mOperationType getType() { + return LwM2mOperationType.OBSERVE_CANCEL; } 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 f6b0bfac81..8bd2e7505f 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 @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.response.DeleteResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; public class TbLwM2MDeleteCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java index 62532c1726..b9649c0708 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDeleteRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MDeleteRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -27,8 +27,8 @@ public class TbLwM2MDeleteRequest extends AbstractTbLwM2MTargetedDownlinkRequest } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.DELETE; + public LwM2mOperationType getType() { + return LwM2mOperationType.DELETE; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java index 9400e3bfba..291bdc907c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverAllRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MDiscoverAllRequest implements TbLwM2MDownlinkRequest { @@ -30,8 +30,8 @@ public class TbLwM2MDiscoverAllRequest implements TbLwM2MDownlinkRequest } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_ALL; + public LwM2mOperationType getType() { + return LwM2mOperationType.DISCOVER_ALL; } 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 dc3269b5a8..e21949f7c4 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 @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.response.DiscoverResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; public class TbLwM2MDiscoverCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java index 6b8319bc25..85aa75de3c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDiscoverRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import org.eclipse.leshan.core.response.DiscoverResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MDiscoverRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -27,8 +27,8 @@ public class TbLwM2MDiscoverRequest extends AbstractTbLwM2MTargetedDownlinkReque } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.DISCOVER; + public LwM2mOperationType getType() { + return LwM2mOperationType.DISCOVER; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java index a069bb4ddc..3ea174c72d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MDownlinkRequest.java @@ -15,11 +15,11 @@ */ package org.thingsboard.server.transport.lwm2m.server.downlink; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public interface TbLwM2MDownlinkRequest { - LwM2mTransportUtil.LwM2mTypeOper getType(); + LwM2mOperationType getType(); long getTimeout(); 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 f43a648cc3..ff7c40b3b9 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 @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.response.ExecuteResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; public class TbLwM2MExecuteCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java index 32d1be665c..76cf8cd0eb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MExecuteRequest.java @@ -18,7 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MExecuteRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -32,8 +32,8 @@ public class TbLwM2MExecuteRequest extends AbstractTbLwM2MTargetedDownlinkReques } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; + public LwM2mOperationType getType() { + return LwM2mOperationType.EXECUTE; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java index b12b4469d6..2aba4f0a58 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveAllRequest.java @@ -17,7 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; import java.util.Set; @@ -32,8 +32,8 @@ public class TbLwM2MObserveAllRequest implements TbLwM2MDownlinkRequest { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java index 60fb9468f3..f3348aa2c9 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MObserveRequest.java @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.ObserveResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { @@ -33,8 +33,8 @@ public class TbLwM2MObserveRequest extends AbstractTbLwM2MTargetedDownlinkReques } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.OBSERVE; + public LwM2mOperationType getType() { + return LwM2mOperationType.OBSERVE; } 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 f142d53c09..9ab30a9410 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 @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; public class TbLwM2MReadCallback extends AbstractTbLwM2MRequestCallback { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java index 5d88a20ae4..a07e738465 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MReadRequest.java @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.request.ContentFormat; import org.eclipse.leshan.core.response.ReadResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest implements HasContentFormat { @@ -33,8 +33,8 @@ public class TbLwM2MReadRequest extends AbstractTbLwM2MTargetedDownlinkRequest { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java index baa7401742..6ef198a28f 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteAttributesRequest.java @@ -19,7 +19,7 @@ import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.response.WriteAttributesResponse; import org.thingsboard.server.common.data.device.data.lwm2m.ObjectAttributes; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MWriteAttributesRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -33,8 +33,8 @@ public class TbLwM2MWriteAttributesRequest extends AbstractTbLwM2MTargetedDownli } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.WRITE_ATTRIBUTES; + public LwM2mOperationType getType() { + return LwM2mOperationType.WRITE_ATTRIBUTES; } 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/TbLwM2MWriteReplaceCallback.java index ecfa9241de..c8ef5622f2 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/TbLwM2MWriteReplaceCallback.java @@ -16,7 +16,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import org.eclipse.leshan.core.response.WriteResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; public class TbLwM2MWriteReplaceCallback extends AbstractTbLwM2MRequestCallback { 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 488942f7de..007aa1d3f8 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 @@ -18,7 +18,7 @@ package org.thingsboard.server.transport.lwm2m.server.downlink; import lombok.Builder; import lombok.Getter; import org.eclipse.leshan.core.response.WriteResponse; -import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -32,8 +32,8 @@ public class TbLwM2MWriteReplaceRequest extends AbstractTbLwM2MTargetedDownlinkR } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.WRITE_REPLACE; + public LwM2mOperationType getType() { + return LwM2mOperationType.WRITE_REPLACE; } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java index 071d9fc889..a9465e082d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/downlink/TbLwM2MWriteUpdateRequest.java @@ -19,7 +19,7 @@ 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.LwM2mTransportUtil; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; public class TbLwM2MWriteUpdateRequest extends AbstractTbLwM2MTargetedDownlinkRequest { @@ -36,8 +36,8 @@ public class TbLwM2MWriteUpdateRequest extends AbstractTbLwM2MTargetedDownlinkRe } @Override - public LwM2mTransportUtil.LwM2mTypeOper getType() { - return LwM2mTransportUtil.LwM2mTypeOper.WRITE_UPDATE; + public LwM2mOperationType getType() { + return LwM2mOperationType.WRITE_UPDATE; } 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 new file mode 100644 index 0000000000..18c4feba67 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java @@ -0,0 +1,195 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.leshan.core.ResponseCode; +import org.eclipse.leshan.server.registration.Registration; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; +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.queue.util.TbLwM2mTransportComponent; +import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +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.TbLwM2MReadCallback; +import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; + +import java.util.Map; +import java.util.Set; +import java.util.UUID; +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; + +@Slf4j +@Service +@TbLwM2mTransportComponent +@RequiredArgsConstructor +public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { + + private final TransportService transportService; + private final LwM2mClientContext clientContext; + private final LwM2MTransportServerConfig config; + private final LwM2mUplinkMsgHandler uplinkHandler; + 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 + 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()); + } + LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); + if (client.getRegistration() == null) { + this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), ResponseCode.INTERNAL_SERVER_ERROR.getName(), "Registration is empty"); + } + switch (operationType) { + case READ: + sendReadRequest(client, rpcRequst); + break; + } + + +// 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 sendReadRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { + String id = getIdFromParameters(rpcRequst); + TbLwM2MReadRequest request = TbLwM2MReadRequest.builder().versionedId(id).timeout(this.config.getTimeout()).build(); + downlinkHandler.sendReadRequest(client, request, new TbLwM2MReadCallback(uplinkHandler, client, id)); + } + + private String getIdFromParameters(TransportProtos.ToDeviceRpcRequestMsg rpcRequst) { + IdOrKeyRequest requestParams = JacksonUtil.fromString(rpcRequst.getParams(), IdOrKeyRequest.class); + String id; + if (StringUtils.isNotEmpty(requestParams.getKey())) { + id = requestParams.getKey(); + } else if (StringUtils.isNotEmpty(requestParams.getId())) { + id = requestParams.getId(); + } else { + throw new IllegalArgumentException("Can't find 'key' or 'id' in the requestParams parameters!"); + } + return id; + } + + private String getTargetId(LwM2mClient client, String params) { + + } + + private void sendErrorRpcResponse(TransportProtos.SessionInfoProto sessionInfo, int requestId, String result, String error) { + String payload = JacksonUtil.toString(JacksonUtil.newObjectNode().put("result", result).put("error", error)); + TransportProtos.ToDeviceRpcResponseMsg msg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder().setRequestId(requestId).setPayload(payload).build(); + transportService.process(sessionInfo, msg, null); + } + + private void sendErrorRpcResponse(LwM2mClientRpcRequest lwm2mClientRpcRequest, String msgError, TransportProtos.SessionInfoProto sessionInfo) { + if (lwm2mClientRpcRequest == null) { + lwm2mClientRpcRequest = new LwM2mClientRpcRequest(); + } + lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); + if (lwm2mClientRpcRequest.getErrorMsg() == null) { + lwm2mClientRpcRequest.setErrorMsg(msgError); + } + this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); + } + + private void cleanupOldSessions() { + log.warn("4.1) before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); + if (rpcSubscriptions.size() > 0) { + long currentTime = System.currentTimeMillis(); + Set rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> currentTime > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); + log.warn("4.2) System.currentTimeMillis(): [{}]", System.currentTimeMillis()); + log.warn("4.3) rpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove); + rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove); + } + log.warn("4.4) after rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); + } + + public void sentRpcResponse(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, TransportProtos.SessionInfoProto sessionInfo) { + log.warn("5) onToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); + transportService.process(sessionInfo, toDeviceResponse, null); + } + + public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { + log.info("[{}] toServerRpcResponse", toServerResponse); + } +} 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 new file mode 100644 index 0000000000..1055b01232 --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/IdOrKeyRequest.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.rpc; + +import lombok.Data; + +@Data +public class IdOrKeyRequest { + + private String key; + private String id; + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHandler.java new file mode 100644 index 0000000000..6d1b0c01bc --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2MRpcRequestHandler.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 org.thingsboard.server.gen.transport.TransportProtos; + +public interface LwM2MRpcRequestHandler { + + 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/client/LwM2mClientRpcRequest.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2mClientRpcRequest.java similarity index 88% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/LwM2mClientRpcRequest.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/LwM2mClientRpcRequest.java index e11d50f29f..07e47555fa 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/rpc/LwM2mClientRpcRequest.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.client; +package org.thingsboard.server.transport.lwm2m.server.rpc; import com.google.gson.Gson; import com.google.gson.JsonObject; @@ -24,7 +24,9 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2MUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import java.util.Map; import java.util.Objects; @@ -36,15 +38,17 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.F import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.FINISH_VALUE_KEY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.INFO_KEY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.KEY_NAME_KEY; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.DISCOVER_ALL; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.EXECUTE; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.FW_UPDATE; -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.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 org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType; + +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.DISCOVER_ALL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.EXECUTE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.FW_UPDATE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_CANCEL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.OBSERVE_READ_ALL; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.WRITE_ATTRIBUTES; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.WRITE_REPLACE; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mOperationType.WRITE_UPDATE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.METHOD_KEY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.PARAMS_KEY; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.RESULT_KEY; @@ -64,7 +68,7 @@ public class LwM2mClientRpcRequest { private String bodyParams; private int requestId; - private LwM2mTypeOper typeOper; + private LwM2mOperationType typeOper; private String key; private String targetIdVer; private Object value; @@ -78,8 +82,8 @@ public class LwM2mClientRpcRequest { public LwM2mClientRpcRequest() { } - public LwM2mClientRpcRequest(LwM2mTypeOper lwM2mTypeOper, String bodyParams, int requestId, - TransportProtos.SessionInfoProto sessionInfo, Registration registration, DefaultLwM2MUplinkMsgHandler handler) { + public LwM2mClientRpcRequest(LwM2mOperationType lwM2mTypeOper, String bodyParams, int requestId, + TransportProtos.SessionInfoProto sessionInfo, Registration registration, LwM2mUplinkMsgHandler handler) { this.registration = registration; this.sessionInfo = sessionInfo; this.requestId = requestId; @@ -88,7 +92,7 @@ public class LwM2mClientRpcRequest { } else { this.errorMsg = METHOD_KEY + " - " + typeOper + " is not valid."; } - if (this.errorMsg == null && !bodyParams.equals("null")) { + if (this.errorMsg == null && !bodyParams.equals("null")) { this.bodyParams = bodyParams; this.init(handler); } @@ -110,7 +114,7 @@ public class LwM2mClientRpcRequest { .build(); } - private void init(DefaultLwM2MUplinkMsgHandler handler) { + private void init(LwM2mUplinkMsgHandler handler) { try { // #1 if (this.bodyParams.contains(KEY_NAME_KEY)) { @@ -179,7 +183,7 @@ public class LwM2mClientRpcRequest { } } - private void setValidParamsKey(DefaultLwM2MUplinkMsgHandler handler) { + private void setValidParamsKey(LwM2mUplinkMsgHandler handler) { String paramsStr = this.getValueKeyFromBody(PARAMS_KEY); if (paramsStr != null) { String params2Json = @@ -245,7 +249,7 @@ public class LwM2mClientRpcRequest { } private ConcurrentHashMap convertParamsToResourceId(ConcurrentHashMap params, - DefaultLwM2MUplinkMsgHandler serviceImpl) { + LwM2mUplinkMsgHandler serviceImpl) { Map paramsIdVer = new ConcurrentHashMap<>(); LwM2mPath targetId = new LwM2mPath(Objects.requireNonNull(fromVersionedIdToObjectId(this.targetIdVer))); if (targetId.isObjectInstance()) { @@ -257,7 +261,7 @@ public class LwM2mClientRpcRequest { String targetIdVer = serviceImpl.getPresentPathIntoProfile(sessionInfo, k); if (targetIdVer != null) { LwM2mPath lwM2mPath = new LwM2mPath(Objects.requireNonNull(fromVersionedIdToObjectId(targetIdVer))); - paramsIdVer.put(String.valueOf(lwM2mPath.getResourceId()), v); + paramsIdVer.put(String.valueOf(lwM2mPath.getResourceId()), v); } /** WRITE_UPDATE*/ else { @@ -272,10 +276,11 @@ public class LwM2mClientRpcRequest { return (ConcurrentHashMap) paramsIdVer; } - private String getRezIdByResourceNameAndObjectInstanceId(String resourceName, DefaultLwM2MUplinkMsgHandler handler) { - LwM2mClient lwM2mClient = handler.clientContext.getClientBySessionInfo(this.sessionInfo); - return lwM2mClient != null ? - lwM2mClient.getRezIdByResourceNameAndObjectInstanceId(resourceName, this.targetIdVer, handler.config.getModelProvider()) : - null; + private String getRezIdByResourceNameAndObjectInstanceId(String resourceName, LwM2mUplinkMsgHandler handler) { +// LwM2mClient lwM2mClient = handler.clientContext.getClientBySessionInfo(this.sessionInfo); +// return lwM2mClient != null ? +// lwM2mClient.getRezIdByResourceNameAndObjectInstanceId(resourceName, this.targetIdVer, handler.config.getModelProvider()) : +// null; + return null; } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java similarity index 91% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java index 55129e59c9..b64345bf54 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/DefaultLwM2MUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.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.uplink; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -21,7 +21,6 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.eclipse.leshan.core.model.ObjectModel; import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mObject; @@ -55,30 +54,36 @@ 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.LwM2mTransportUtil.LwM2mTypeOper; +import org.thingsboard.server.transport.lwm2m.server.LwM2mOtaConvert; +import org.thingsboard.server.transport.lwm2m.server.LwM2mQueuedRequest; +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 org.thingsboard.server.transport.lwm2m.server.adaptors.LwM2MJsonAdaptor; 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.LwM2mClientRpcRequest; 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.LwM2mDownlinkMsgHandler; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MCancelObserveRequest; 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.TbLwM2MObserveRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MObserveCallback; -import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MReadRequest; +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.TbLwM2MWriteReplaceCallback; 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; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MDtlsSessionStore; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; @@ -98,7 +103,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; -import static org.eclipse.californium.core.coap.CoAP.ResponseCode.BAD_REQUEST; 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.common.data.ota.OtaPackageUpdateStatus.INITIATED; @@ -110,16 +114,14 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.F 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_VALUE; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LOG_LW2M_WARN; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LwM2mTypeOper.READ; +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; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.fromVersionedIdToObjectId; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getAckCallback; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.isFwSwWords; -import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.setValidTypeOper; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.validateObjectVerFromKey; @@ -141,12 +143,14 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { private final LwM2MJsonAdaptor adaptor; private final TbLwM2MDtlsSessionStore sessionStore; public final LwM2mClientContext clientContext; + private final LwM2MRpcRequestHandler rpcHandler; public final LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler; - private final Map rpcSubscriptions; + public final Map firmwareUpdateState; public DefaultLwM2MUplinkMsgHandler(TransportService transportService, LwM2MTransportServerConfig config, LwM2mTransportServerHelper helper, LwM2mClientContext clientContext, + @Lazy LwM2MRpcRequestHandler rpcHandler, @Lazy LwM2mDownlinkMsgHandler defaultLwM2MDownlinkMsgHandler, OtaPackageDataCache otaPackageDataCache, LwM2mTransportContext context, LwM2MJsonAdaptor adaptor, TbLwM2MDtlsSessionStore sessionStore) { @@ -154,11 +158,11 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { this.config = config; this.helper = helper; this.clientContext = clientContext; + this.rpcHandler = rpcHandler; this.defaultLwM2MDownlinkMsgHandler = defaultLwM2MDownlinkMsgHandler; this.otaPackageDataCache = otaPackageDataCache; this.context = context; this.adaptor = adaptor; - this.rpcSubscriptions = new ConcurrentHashMap<>(); this.firmwareUpdateState = new ConcurrentHashMap<>(); this.sessionStore = sessionStore; } @@ -195,7 +199,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { this.clientContext.register(lwM2MClient, registration); this.sendLogsToThingsboard(lwM2MClient, LOG_LW2M_INFO + ": Client registered with registration id: " + registration.getId()); SessionInfoProto sessionInfo = lwM2MClient.getSession(); - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); + transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, rpcHandler, sessionInfo)); log.warn("40) sessionId [{}] Registering rpc subscription after Registration client", new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); TransportProtos.TransportToDeviceActorMsg msg = TransportProtos.TransportToDeviceActorMsg.newBuilder() .setSessionInfo(sessionInfo) @@ -350,7 +354,8 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { READ, pathIdVer, value); this.sendLogsToThingsboard(lwM2MClient, msg); rpcRequest.setValueMsg(String.format("%s", value)); - this.sentRpcResponse(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); + //TODO: refactor +// this.sentRpcResponse(rpcRequest, response.getCode().getName(), (String) value, LOG_LW2M_VALUE); } /** @@ -452,98 +457,6 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { clientContext.getLwM2mClients().forEach(e -> e.deleteResources(pathIdVer, this.config.getModelProvider())); } - /** - * #1 del from rpcSubscriptions by timeout - * #2 if not present in rpcSubscriptions by requestId: create new LwM2mClientRpcRequest, after success - add requestId, timeout - */ - @Override - public void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg, SessionInfoProto sessionInfo) { - // #1 - this.checkRpcRequestTimeout(); - log.warn("4) toDeviceRpcRequestMsg: [{}], sessionUUID: [{}]", toDeviceRpcRequestMsg, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); - String bodyParams = StringUtils.trimToNull(toDeviceRpcRequestMsg.getParams()) != null ? toDeviceRpcRequestMsg.getParams() : "null"; - LwM2mTypeOper lwM2mTypeOper = setValidTypeOper(toDeviceRpcRequestMsg.getMethodName()); - UUID requestUUID = new UUID(toDeviceRpcRequestMsg.getRequestIdMSB(), toDeviceRpcRequestMsg.getRequestIdLSB()); - if (!this.rpcSubscriptions.containsKey(requestUUID)) { - this.rpcSubscriptions.put(requestUUID, toDeviceRpcRequestMsg.getExpirationTime()); - LwM2mClientRpcRequest lwm2mClientRpcRequest = null; - try { - LwM2mClient client = clientContext.getClientBySessionInfo(sessionInfo); - Registration registration = client.getRegistration(); - if (registration != null) { - lwm2mClientRpcRequest = new LwM2mClientRpcRequest(lwM2mTypeOper, bodyParams, toDeviceRpcRequestMsg.getRequestId(), sessionInfo, registration, this); - 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 sendErrorRpcResponse(LwM2mClientRpcRequest lwm2mClientRpcRequest, String msgError, SessionInfoProto sessionInfo) { - if (lwm2mClientRpcRequest == null) { - lwm2mClientRpcRequest = new LwM2mClientRpcRequest(); - } - lwm2mClientRpcRequest.setResponseCode(BAD_REQUEST.name()); - if (lwm2mClientRpcRequest.getErrorMsg() == null) { - lwm2mClientRpcRequest.setErrorMsg(msgError); - } - this.onToDeviceRpcResponse(lwm2mClientRpcRequest.getDeviceRpcResponseResultMsg(), sessionInfo); - } - - private void checkRpcRequestTimeout() { - log.warn("4.1) before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); - if (rpcSubscriptions.size() > 0) { - Set rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> System.currentTimeMillis() > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); - log.warn("4.2) System.currentTimeMillis(): [{}]", System.currentTimeMillis()); - log.warn("4.3) rpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove); - rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove); - } - log.warn("4.4) after rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); - } - - public void sentRpcResponse(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) { - log.warn("5) onToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); - transportService.process(sessionInfo, toDeviceResponse, null); - } - - public void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse) { - log.info("[{}] toServerRpcResponse", toServerResponse); - } - /** * Deregister session in transport * @@ -1065,6 +978,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param updateCredentials - Credentials include config only security Client (without config attr/telemetry...) * config attr/telemetry... in profile */ + @Override public void onToTransportUpdateCredentials(TransportProtos.ToTransportUpdateCredentialsProto updateCredentials) { log.info("[{}] idList [{}] valueList updateCredentials", updateCredentials.getCredentialsIdList(), updateCredentials.getCredentialsValueList()); } @@ -1076,6 +990,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param name - * @return - */ + @Override public String getPresentPathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name) { var profile = clientContext.getProfile(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); LwM2mClient lwM2mClient = clientContext.getClientBySessionInfo(sessionInfo); @@ -1093,6 +1008,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { * @param attributesResponse - * @param sessionInfo - */ + @Override public void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo) { try { List tsKvProtos = attributesResponse.getSharedAttributeListList(); @@ -1163,7 +1079,7 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { */ private void reportActivityAndRegister(SessionInfoProto sessionInfo) { if (sessionInfo != null && transportService.reportActivity(sessionInfo) == null) { - transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, sessionInfo)); + transportService.registerAsyncSession(sessionInfo, new LwM2mSessionMsgListener(this, rpcHandler, sessionInfo)); this.reportActivitySubscription(sessionInfo); } } @@ -1236,7 +1152,8 @@ public class DefaultLwM2MUplinkMsgHandler implements LwM2mUplinkMsgHandler { lwM2MClient.getDeviceName(), response.getResponseStatus().toString()); log.trace(msgError); if (rpcRequest != null) { - sendErrorRpcResponse(rpcRequest, msgError, sessionInfo); + //TODO: refactor +// sendErrorRpcResponse(rpcRequest, msgError, sessionInfo); } } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mUplinkMsgHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java similarity index 83% rename from common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mUplinkMsgHandler.java rename to common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.java index 132bd8689d..91985363eb 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/LwM2mUplinkMsgHandler.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/LwM2mUplinkMsgHandler.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.uplink; import org.eclipse.leshan.core.observation.Observation; import org.eclipse.leshan.core.response.ReadResponse; @@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.gen.transport.TransportProtos; 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.LwM2mClientRpcRequest; +import org.thingsboard.server.transport.lwm2m.server.rpc.LwM2mClientRpcRequest; import java.util.Collection; import java.util.Optional; @@ -50,12 +50,6 @@ public interface LwM2mUplinkMsgHandler { void onResourceDelete(Optional resourceDeleteMsgOpt); - void onToDeviceRpcRequest(TransportProtos.ToDeviceRpcRequestMsg toDeviceRequest, TransportProtos.SessionInfoProto sessionInfo); - - void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceRpcResponse, TransportProtos.SessionInfoProto sessionInfo); - - void onToServerRpcResponse(TransportProtos.ToServerRpcResponseMsg toServerResponse); - void doDisconnect(TransportProtos.SessionInfoProto sessionInfo); void onAwakeDev(Registration registration); @@ -64,5 +58,11 @@ public interface LwM2mUplinkMsgHandler { void sendLogsToThingsboard(String registrationId, String msg); + void onToTransportUpdateCredentials(TransportProtos.ToTransportUpdateCredentialsProto updateCredentials); + + String getPresentPathIntoProfile(TransportProtos.SessionInfoProto sessionInfo, String name); + + void onGetAttributesResponse(TransportProtos.GetAttributeResponseMsg attributesResponse, TransportProtos.SessionInfoProto sessionInfo); + LwM2MTransportServerConfig getConfig(); } From e58d5b2d8a7e3760525ce5c9a0f497b299a9625d Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 14 Jun 2021 15:06:21 +0300 Subject: [PATCH 045/231] removed rpc status sent, added idx_rpc_tenant_id_device_id --- .../main/data/upgrade/3.2.2/schema_update.sql | 2 + .../device/DeviceActorMessageProcessor.java | 52 +++++++--------- .../server/controller/RpcController.java | 39 +++++++++++- .../action/RuleEngineEntityActionService.java | 2 +- .../server/service/rpc/TbRpcService.java | 11 ++-- .../server/dao/rpc/RpcService.java | 8 ++- .../server/common/data/DataConstants.java | 1 - .../server/common/data/rpc/RpcStatus.java | 2 +- .../server/dao/rpc/BaseRpcService.java | 60 +++++++++++++++---- .../thingsboard/server/dao/rpc/RpcDao.java | 4 +- .../server/dao/sql/rpc/JpaRpcDao.java | 8 ++- .../server/dao/sql/rpc/RpcRepository.java | 4 +- .../server/dao/tenant/TenantServiceImpl.java | 5 ++ .../resources/sql/schema-entities-idx.sql | 1 + .../engine/filter/TbMsgTypeSwitchNode.java | 4 +- .../src/app/shared/models/rule-node.models.ts | 6 +- 16 files changed, 147 insertions(+), 62 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 713793b341..d4404427f2 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 @@ -209,3 +209,5 @@ CREATE TABLE IF NOT EXISTS rpc ( status varchar(255) NOT NULL ); +CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id); + 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 8657313633..a1458fd5ac 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 @@ -174,20 +174,15 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { long timeout = request.getExpirationTime() - System.currentTimeMillis(); boolean persisted = request.isPersisted(); + if (timeout <= 0) { log.debug("[{}][{}] Ignoring message due to exp time reached, {}", deviceId, request.getId(), request.getExpirationTime()); - if (persisted) { - Rpc rpc = new Rpc(new RpcId(request.getId())); - rpc.setCreatedTime(System.currentTimeMillis()); - rpc.setTenantId(tenantId); - rpc.setDeviceId(deviceId); - rpc.setExpirationTime(request.getExpirationTime()); - rpc.setRequest(JacksonUtil.valueToTree(request)); - rpc.setStatus(RpcStatus.TIMEOUT); - systemContext.getTbRpcService().save(tenantId, rpc); + createRpc(request, RpcStatus.TIMEOUT); } return; + } else if (persisted) { + createRpc(request, RpcStatus.QUEUED); } boolean sent; @@ -204,24 +199,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { syncSessionSet.add(key); } }); - log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]",syncSessionSet, rpcSubscriptions); + log.trace("46) Rpc syncSessionSet [{}] subscription after sent [{}]", syncSessionSet, rpcSubscriptions); syncSessionSet.forEach(rpcSubscriptions::remove); } - if (persisted) { - Rpc rpc = new Rpc(new RpcId(request.getId())); - rpc.setCreatedTime(System.currentTimeMillis()); - rpc.setTenantId(tenantId); - rpc.setDeviceId(deviceId); - rpc.setExpirationTime(request.getExpirationTime()); - rpc.setRequest(JacksonUtil.valueToTree(request)); - rpc.setStatus(sent ? RpcStatus.SENT : RpcStatus.QUEUED); - systemContext.getTbRpcService().save(tenantId, rpc); - if (!(sent || request.isOneway())) { - ObjectNode response = JacksonUtil.newObjectNode(); - response.put("rpcId", request.getId().toString()); - systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), JacksonUtil.toString(response), null)); - } + if (persisted && !(sent || request.isOneway())) { + ObjectNode response = JacksonUtil.newObjectNode(); + response.put("rpcId", request.getId().toString()); + systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(msg.getMsg().getId(), JacksonUtil.toString(response), null)); } if (request.isOneway() && sent) { @@ -237,6 +222,18 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } } + private Rpc createRpc(ToDeviceRpcRequest request, RpcStatus status) { + Rpc rpc = new Rpc(new RpcId(request.getId())); + rpc.setCreatedTime(System.currentTimeMillis()); + rpc.setTenantId(tenantId); + rpc.setDeviceId(deviceId); + rpc.setExpirationTime(request.getExpirationTime()); + rpc.setRequest(JacksonUtil.valueToTree(request)); + rpc.setStatus(status); + systemContext.getTbRpcService().save(tenantId, rpc); + return systemContext.getTbRpcService().save(tenantId, rpc); + } + private ToDeviceRpcRequestMsg creteToDeviceRpcRequestMsg(ToDeviceRpcRequest request) { ToDeviceRpcRequestBody body = request.getBody(); return ToDeviceRpcRequestMsg.newBuilder() @@ -318,9 +315,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { .setPersisted(request.isPersisted()) .build(); - if (request.isPersisted()) { - systemContext.getTbRpcService().save(tenantId, new RpcId(request.getId()), RpcStatus.SENT, null); - } sendToTransport(rpcRequest, sessionId, nodeId); }; } @@ -821,10 +815,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { void init(TbActorCtx ctx) { schedulePeriodicMsgWithDelay(ctx, SessionTimeoutCheckMsg.instance(), systemContext.getSessionReportTimeout(), systemContext.getSessionReportTimeout()); - PageLink pageLink = new PageLink(10); + PageLink pageLink = new PageLink(1024); PageData pageData; do { - pageData = systemContext.getTbRpcService().findAllByDeviceIdAndStatus(deviceId, RpcStatus.QUEUED, pageLink); + pageData = systemContext.getTbRpcService().findAllByDeviceIdAndStatus(tenantId, deviceId, RpcStatus.QUEUED, pageLink); pageData.getData().forEach(rpc -> { ToDeviceRpcRequest msg = JacksonUtil.convertValue(rpc.getRequest(), ToDeviceRpcRequest.class); long timeout = rpc.getExpirationTime() - System.currentTimeMillis(); diff --git a/application/src/main/java/org/thingsboard/server/controller/RpcController.java b/application/src/main/java/org/thingsboard/server/controller/RpcController.java index 8a75aad485..eae26542fa 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RpcController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RpcController.java @@ -29,6 +29,7 @@ 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.context.request.async.DeferredResult; @@ -41,7 +42,10 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RpcId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -95,7 +99,7 @@ public class RpcController extends BaseController { return handleDeviceRPCRequest(false, new DeviceId(UUID.fromString(deviceIdStr)), requestBody); } - @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/persisted/{rpcId}", method = RequestMethod.GET) @ResponseBody public Rpc getPersistedRpc(@PathVariable("rpcId") String strRpc) throws ThingsboardException { @@ -108,6 +112,39 @@ public class RpcController extends BaseController { } } + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") + @RequestMapping(value = "/persisted/{deviceId}", method = RequestMethod.GET) + @ResponseBody + public PageData getPersistedRpcByDevice(@PathVariable("deviceId") String strDeviceId, + @RequestParam int pageSize, + @RequestParam int page, + @RequestParam RpcStatus rpcStatus, + @RequestParam(required = false) String textSearch, + @RequestParam(required = false) String sortProperty, + @RequestParam(required = false) String sortOrder) throws ThingsboardException { + checkParameter("DeviceId", strDeviceId); + try { + TenantId tenantId = getCurrentUser().getTenantId(); + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); + DeviceId deviceId = new DeviceId(UUID.fromString(strDeviceId)); + return checkNotNull(rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink)); + } catch (Exception e) { + throw handleException(e); + } + } + + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/persisted/{rpcId}", method = RequestMethod.DELETE) + @ResponseBody + public void deleteResource(@PathVariable("rpcId") String strRpc) throws ThingsboardException { + checkParameter("RpcId", strRpc); + try { + rpcService.deleteRpc(getTenantId(), new RpcId(UUID.fromString(strRpc))); + } catch (Exception e) { + throw handleException(e); + } + } + private DeferredResult handleDeviceRPCRequest(boolean oneWay, DeviceId deviceId, String requestBody) throws ThingsboardException { try { JsonNode rpcRequestBody = jsonMapper.readTree(requestBody); diff --git a/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java b/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java index 754d32b704..f1320d1a7a 100644 --- a/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java +++ b/application/src/main/java/org/thingsboard/server/service/action/RuleEngineEntityActionService.java @@ -54,7 +54,7 @@ public class RuleEngineEntityActionService { private static final ObjectMapper json = new ObjectMapper(); - public void pushEntityActionToRuleEngine(EntityId entityId, Object entity, TenantId tenantId, CustomerId customerId, + public void pushEntityActionToRuleEngine(EntityId entityId, HasName entity, TenantId tenantId, CustomerId customerId, ActionType actionType, User user, Object... additionalInfo) { String msgType = null; switch (actionType) { diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java index d410f12b1f..e0343485db 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/TbRpcService.java @@ -41,9 +41,10 @@ public class TbRpcService { private final RpcService rpcService; private final TbClusterService tbClusterService; - public void save(TenantId tenantId, Rpc rpc) { - Rpc saved = rpcService.save(tenantId, rpc); + public Rpc save(TenantId tenantId, Rpc rpc) { + Rpc saved = rpcService.save(rpc); pushRpcMsgToRuleEngine(tenantId, saved); + return saved; } public void save(TenantId tenantId, RpcId rpcId, RpcStatus newStatus, JsonNode response) { @@ -53,7 +54,7 @@ public class TbRpcService { if (response != null) { foundRpc.setResponse(response); } - Rpc saved = rpcService.save(tenantId, foundRpc); + Rpc saved = rpcService.save(foundRpc); pushRpcMsgToRuleEngine(tenantId, saved); } else { log.warn("[{}] Failed to update RPC status because RPC was already deleted", rpcId); @@ -69,8 +70,8 @@ public class TbRpcService { return rpcService.findById(tenantId, rpcId); } - public PageData findAllByDeviceIdAndStatus(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { - return rpcService.findAllByDeviceIdAndStatus(deviceId, rpcStatus, pageLink); + public PageData findAllByDeviceIdAndStatus(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { + return rpcService.findAllByDeviceIdAndStatus(tenantId, deviceId, rpcStatus, pageLink); } } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java index 22446d143e..4bdb1a169d 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/rpc/RpcService.java @@ -25,13 +25,15 @@ import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.RpcStatus; public interface RpcService { - Rpc save(TenantId tenantId, Rpc rpc); + Rpc save(Rpc rpc); - void remove(TenantId tenantId, RpcId id); + void deleteRpc(TenantId tenantId, RpcId id); + + void deleteAllRpcByTenantId(TenantId tenantId); Rpc findById(TenantId tenantId, RpcId id); ListenableFuture findRpcByIdAsync(TenantId tenantId, RpcId id); - PageData findAllByDeviceIdAndStatus(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink); + PageData findAllByDeviceIdAndStatus(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, 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 de97bf324c..efc81846d5 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 @@ -77,7 +77,6 @@ public class DataConstants { public static final String RPC_CALL_FROM_SERVER_TO_DEVICE = "RPC_CALL_FROM_SERVER_TO_DEVICE"; public static final String RPC_QUEUED = "RPC_QUEUED"; - public static final String RPC_SENT = "RPC_SENT"; public static final String RPC_DELIVERED = "RPC_DELIVERED"; public static final String RPC_SUCCESSFUL = "RPC_SUCCESSFUL"; public static final String RPC_TIMEOUT = "RPC_TIMEOUT"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java index fea9e30a75..c80d0c5993 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/rpc/RpcStatus.java @@ -16,5 +16,5 @@ package org.thingsboard.server.common.data.rpc; public enum RpcStatus { - QUEUED, SENT, DELIVERED, SUCCESSFUL, TIMEOUT, FAILED + QUEUED, DELIVERED, SUCCESSFUL, TIMEOUT, FAILED } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java b/dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java index 577263a3bd..02b4bbe433 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rpc/BaseRpcService.java @@ -26,35 +26,75 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.RpcStatus; +import org.thingsboard.server.dao.service.PaginatedRemover; + +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validatePageLink; @Service @Slf4j @RequiredArgsConstructor public class BaseRpcService implements RpcService { + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + public static final String INCORRECT_RPC_ID = "Incorrect rpcId "; + private final RpcDao rpcDao; @Override - public Rpc save(TenantId tenantId, Rpc rpc) { - return rpcDao.save(tenantId, rpc); + public Rpc save(Rpc rpc) { + log.trace("Executing save, [{}]", rpc); + return rpcDao.save(rpc.getTenantId(), rpc); } @Override - public void remove(TenantId tenantId, RpcId id) { - rpcDao.removeById(tenantId, id.getId()); + public void deleteRpc(TenantId tenantId, RpcId rpcId) { + log.trace("Executing deleteRpc, tenantId [{}], rpcId [{}]", tenantId, rpcId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(rpcId, INCORRECT_RPC_ID + rpcId); + rpcDao.removeById(tenantId, rpcId.getId()); } @Override - public Rpc findById(TenantId tenantId, RpcId id) { - return rpcDao.findById(tenantId, id.getId()); + public void deleteAllRpcByTenantId(TenantId tenantId) { + log.trace("Executing deleteAllRpcByTenantId, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + tenantRpcRemover.removeEntities(tenantId, tenantId); } @Override - public ListenableFuture findRpcByIdAsync(TenantId tenantId, RpcId id) { - return rpcDao.findByIdAsync(tenantId, id.getId()); + public Rpc findById(TenantId tenantId, RpcId rpcId) { + log.trace("Executing findById, tenantId [{}], rpcId [{}]", tenantId, rpcId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(rpcId, INCORRECT_RPC_ID + rpcId); + return rpcDao.findById(tenantId, rpcId.getId()); } @Override - public PageData findAllByDeviceIdAndStatus(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { - return rpcDao.findAllByDeviceId(deviceId, rpcStatus, pageLink); + public ListenableFuture findRpcByIdAsync(TenantId tenantId, RpcId rpcId) { + log.trace("Executing findRpcByIdAsync, tenantId [{}], rpcId: [{}]", tenantId, rpcId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(rpcId, INCORRECT_RPC_ID + rpcId); + return rpcDao.findByIdAsync(tenantId, rpcId.getId()); } + + @Override + public PageData findAllByDeviceIdAndStatus(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { + log.trace("Executing findAllByDeviceIdAndStatus, tenantId [{}], deviceId [{}], rpcStatus [{}], pageLink [{}]", tenantId, deviceId, rpcStatus, pageLink); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validatePageLink(pageLink); + return rpcDao.findAllByDeviceId(tenantId, deviceId, rpcStatus, pageLink); + } + + private PaginatedRemover tenantRpcRemover = + new PaginatedRemover<>() { + @Override + protected PageData findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { + return rpcDao.findAllRpcByTenantId(id, pageLink); + } + + @Override + protected void removeEntity(TenantId tenantId, Rpc entity) { + deleteRpc(tenantId, entity.getId()); + } + }; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java index a3aa32720a..63af784dbb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rpc/RpcDao.java @@ -24,7 +24,9 @@ import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.dao.Dao; public interface RpcDao extends Dao { - PageData findAllByDeviceId(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink); + PageData findAllByDeviceId(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink); + + PageData findAllRpcByTenantId(TenantId tenantId, PageLink pageLink); Long deleteOutdatedRpcByTenantId(TenantId tenantId, Long expirationTime); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java index 22fc3396f7..221ef17361 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/JpaRpcDao.java @@ -49,10 +49,14 @@ public class JpaRpcDao extends JpaAbstractDao implements RpcDao return rpcRepository; } + @Override + public PageData findAllByDeviceId(TenantId tenantId, DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { + return DaoUtil.toPageData(rpcRepository.findAllByTenantIdAndDeviceIdAndStatus(tenantId.getId(), deviceId.getId(), rpcStatus, DaoUtil.toPageable(pageLink))); + } @Override - public PageData findAllByDeviceId(DeviceId deviceId, RpcStatus rpcStatus, PageLink pageLink) { - return DaoUtil.toPageData(rpcRepository.findAllByDeviceIdAndStatus(deviceId.getId(), rpcStatus, DaoUtil.toPageable(pageLink))); + public PageData findAllRpcByTenantId(TenantId tenantId, PageLink pageLink) { + return DaoUtil.toPageData(rpcRepository.findAllByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink))); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java index 565d87e627..76b67b3823 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/rpc/RpcRepository.java @@ -26,7 +26,9 @@ import org.thingsboard.server.dao.model.sql.RpcEntity; import java.util.UUID; public interface RpcRepository extends CrudRepository { - Page findAllByDeviceIdAndStatus(UUID deviceId, RpcStatus status, Pageable pageable); + Page findAllByTenantIdAndDeviceIdAndStatus(UUID tenantId, UUID deviceId, RpcStatus status, Pageable pageable); + + Page findAllByTenantId(UUID tenantId, Pageable pageable); @Query(value = "WITH deleted AS (DELETE FROM rpc WHERE (tenant_id = :tenantId AND created_time < :expirationTime) IS TRUE RETURNING *) SELECT count(*) FROM deleted", nativeQuery = true) 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 c5050467dd..eab0a070f5 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 @@ -37,6 +37,7 @@ import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.resource.ResourceService; +import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; @@ -96,6 +97,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private OtaPackageService otaPackageService; + @Autowired + private RpcService rpcService; + @Override public Tenant findTenantById(TenantId tenantId) { log.trace("Executing findTenantById [{}]", tenantId); @@ -151,6 +155,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe apiUsageStateService.deleteApiUsageStateByTenantId(tenantId); resourceService.deleteResourcesByTenantId(tenantId); otaPackageService.deleteOtaPackagesByTenantId(tenantId); + rpcService.deleteAllRpcByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); deleteEntityRelations(tenantId, tenantId); } diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 28bbe4311e..d649e44418 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -46,3 +46,4 @@ CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribu CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time); +CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index 235bdcfa44..85c2a156b3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -33,7 +33,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; type = ComponentType.FILTER, name = "message type switch", configClazz = EmptyNodeConfiguration.class, - relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "RPC Queued", "RPC Sent", "RPC Delivered", "RPC Successful", "RPC Timeout", "RPC Failed", + relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "RPC Queued", "RPC Delivered", "RPC Successful", "RPC Timeout", "RPC Failed", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned", "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant", "Timeseries Updated", "Timeseries Deleted"}, @@ -97,8 +97,6 @@ public class TbMsgTypeSwitchNode implements TbNode { relationType = "Timeseries Deleted"; } else if (msg.getType().equals(DataConstants.RPC_QUEUED)) { relationType = "RPC Queued"; - } else if (msg.getType().equals(DataConstants.RPC_SENT)) { - relationType = "RPC Sent"; } else if (msg.getType().equals(DataConstants.RPC_DELIVERED)) { relationType = "RPC Delivered"; } else if (msg.getType().equals(DataConstants.RPC_SUCCESSFUL)) { diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index 49c3f18bf8..489c6eef10 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -354,9 +354,8 @@ export enum MessageType { TIMESERIES_UPDATED = 'TIMESERIES_UPDATED', TIMESERIES_DELETED = 'TIMESERIES_DELETED', RPC_QUEUED = 'RPC_QUEUED', - RPC_SENT = 'RPC_SENT', - RPC_DELIVERED = 'RPC_SENT', - RPC_SUCCESSFUL = 'RPC_DELIVERED', + RPC_DELIVERED = 'RPC_DELIVERED', + RPC_SUCCESSFUL = 'RPC_SUCCESSFUL', RPC_TIMEOUT = 'RPC_TIMEOUT', RPC_FAILED = 'RPC_FAILED' } @@ -381,7 +380,6 @@ export const messageTypeNames = new Map( [MessageType.TIMESERIES_UPDATED, 'Timeseries Updated'], [MessageType.TIMESERIES_DELETED, 'Timeseries Deleted'], [MessageType.RPC_QUEUED, 'RPC Queued'], - [MessageType.RPC_SENT, 'RPC Sent'], [MessageType.RPC_DELIVERED, 'RPC Delivered'], [MessageType.RPC_SUCCESSFUL, 'RPC Successful'], [MessageType.RPC_TIMEOUT, 'RPC Timeout'], From f862034f79172e4e32d23c17e0b03c0bf098831f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 14 Jun 2021 18:40:14 +0300 Subject: [PATCH 046/231] 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 6d025d6d5e0edc6274bafc5a974491afe732e200 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 15 Jun 2021 13:45:36 +0300 Subject: [PATCH 047/231] 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 048/231] 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 049/231] 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 050/231] 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 051/231] 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 057/231] 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 058/231] 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 059/231] 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 060/231] 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 061/231] 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 062/231] 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 063/231] 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 064/231] 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 065/231] 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 066/231] 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 067/231] 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 068/231] 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 069/231] 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 070/231] 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 071/231] 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 072/231] 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 073/231] 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 074/231] 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 075/231] 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 076/231] 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 077/231] 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 078/231] 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 079/231] 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 080/231] 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 081/231] 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 082/231] 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 083/231] 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 084/231] 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 085/231] 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 086/231] 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 087/231] 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 088/231] 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 089/231] 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 090/231] 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 091/231] 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 092/231] 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 093/231] 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 094/231] 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 095/231] 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 096/231] 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 097/231] 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 098/231] 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 099/231] 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 100/231] 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 101/231] 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 102/231] 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 103/231] 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 104/231] 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 105/231] 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 106/231] 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 107/231] 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 108/231] 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 109/231] 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 110/231] 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 111/231] 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 112/231] 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 113/231] 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 114/231] 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 115/231] 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 116/231] 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 117/231] 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 118/231] 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 119/231] 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 120/231] 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 121/231] 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 122/231] 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 123/231] 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 124/231] 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 125/231] 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 126/231] 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 127/231] 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 128/231] 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 129/231] 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 130/231] 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 131/231] 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 132/231] 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 133/231] 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 134/231] 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 15473c040a6f53f65e7892976d44f473263d07f3 Mon Sep 17 00:00:00 2001 From: deaflynx Date: Wed, 23 Jun 2021 11:58:04 +0300 Subject: [PATCH 135/231] Hide Manage credentials for edge_customer_user --- ui-ngx/src/app/modules/home/pages/device/device.component.html | 2 +- ui-ngx/src/app/modules/home/pages/device/device.component.ts | 2 +- .../modules/home/pages/device/devices-table-config.resolver.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 2dd6486eeb..b95dbb6584 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 @@ -38,7 +38,7 @@ [disabled]="(isLoading$ | async)" (click)="onEntityAction($event, 'manageCredentials')" [fxShow]="!isEdit"> - {{ (deviceScope === 'customer_user' ? 'device.view-credentials' : 'device.manage-credentials') | translate }} + {{ ((deviceScope === 'customer_user' || deviceScope === 'edge_customer_user') ? 'device.view-credentials' : 'device.manage-credentials') | translate }}
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 @@
- + - + -
- - - -
-
-
- {{'device-profile.lwm2m.instance-label' | translate}} <{{instances.get('id').value}}> -
-
- - -
-
- - -
-
- - -
-
-
-
- - -
-
-
-
-
- - - - -
-
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss index afed0db1ff..c619a2b5ce 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry.component.scss @@ -13,23 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -.tb-panel-title-height { - user-select: none; - min-height: 32px; -} - -.checkbox-padding { - padding-left: 22px; - text-align:center; -} - :host{ section { padding: 2px; } - .instance-list { - mat-expansion-panel-header { - color: inherit; - } + + .tb-panel-title-height { + user-select: none; + min-height: 32px; } } 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 530679113c..e4d0d9acbd 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,38 +14,29 @@ /// limitations under the License. /// -import { Component, forwardRef, Input } from '@angular/core'; +import { ChangeDetectorRef, Component, forwardRef, Input, OnDestroy } from '@angular/core'; import { AbstractControl, ControlValueAccessor, FormArray, FormBuilder, FormGroup, + NG_VALIDATORS, NG_VALUE_ACCESSOR, + ValidationErrors, + Validator, Validators } from '@angular/forms'; -import { Store } from '@ngrx/store'; -import { AppState } from '@core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { - ATTRIBUTE, - ATTRIBUTE_LWM2M, - CLIENT_LWM2M, - Instance, - INSTANCES, - ObjectLwM2M, - ResourceLwM2M, - RESOURCES, - TELEMETRY -} from './lwm2m-profile-config.models'; -import { deepClone, isDefinedAndNotNull, isEqual, isUndefined } from '@core/utils'; +import { Instance, ObjectLwM2M } from './lwm2m-profile-config.models'; +import { deepClone, isDefinedAndNotNull, isEqual } from '@core/utils'; import { MatDialog } from '@angular/material/dialog'; -import { TranslateService } from '@ngx-translate/core'; import { Lwm2mObjectAddInstancesData, Lwm2mObjectAddInstancesDialogComponent } from '@home/components/profile/device/lwm2m/lwm2m-object-add-instances-dialog.component'; import _ from 'lodash'; +import { Subscription } from 'rxjs'; @Component({ selector: 'tb-profile-lwm2m-observe-attr-telemetry', @@ -56,18 +47,20 @@ import _ from 'lodash'; provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryComponent), multi: true + }, + { + provide: NG_VALIDATORS, + useExisting: forwardRef(() => Lwm2mObserveAttrTelemetryComponent), + multi: true } ] }) -export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor { +export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor, OnDestroy, Validator { - private requiredValue: boolean; - - valuePrev = null as any; - observeAttrTelemetryFormGroup: FormGroup; - resources = RESOURCES; + modelsFormGroup: FormGroup; + private requiredValue: boolean; get required(): boolean { return this.requiredValue; } @@ -84,132 +77,103 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor @Input() disabled: boolean; - constructor(private store: Store, - private fb: FormBuilder, + private valueChange$: Subscription = null; + private propagateChange = (v: any) => { }; + + constructor(private fb: FormBuilder, private dialog: MatDialog, - public translate: TranslateService) { - this.observeAttrTelemetryFormGroup = this.fb.group({ - [CLIENT_LWM2M]: this.fb.array([]) - }); - this.observeAttrTelemetryFormGroup.valueChanges.subscribe(value => { - if (isUndefined(this.disabled) || !this.disabled) { - this.propagateChangeState(value); - } + private cd: ChangeDetectorRef) { + this.modelsFormGroup = this.fb.group({ + models: this.fb.array([]) }); } - private propagateChange = (v: any) => { - }; + ngOnDestroy() { + if (this.valueChange$) { + this.valueChange$.unsubscribe(); + } + } registerOnChange(fn: any): void { this.propagateChange = fn; } - private propagateChangeState = (value: any): void => { - if (value) { - if (this.valuePrev === null) { - this.valuePrev = 'init'; - } else if (this.valuePrev === 'init') { - this.valuePrev = value; - } else if (JSON.stringify(value) !== JSON.stringify(this.valuePrev)) { - this.valuePrev = value; - if (this.observeAttrTelemetryFormGroup.valid) { - this.propagateChange(value); - } else { - this.propagateChange(null); - } - } - } - } - registerOnTouched(fn: any): void { } setDisabledState(isDisabled: boolean): void { this.disabled = isDisabled; - this.valuePrev = null; if (isDisabled) { - this.observeAttrTelemetryFormGroup.disable(); + this.modelsFormGroup.disable({emitEvent: false}); } else { - this.observeAttrTelemetryFormGroup.enable(); + this.modelsFormGroup.enable({emitEvent: false}); } } - writeValue(value: {}): void { + writeValue(value: ObjectLwM2M[]): void { if (isDefinedAndNotNull(value)) { - this.buildClientObjectsLwM2M(value[CLIENT_LWM2M]); + this.updateModels(value); } } - private buildClientObjectsLwM2M = (objectsLwM2M: ObjectLwM2M []): void => { - this.observeAttrTelemetryFormGroup.setControl(CLIENT_LWM2M, - this.createObjectsLwM2M(objectsLwM2M) - ); + validate(): ValidationErrors | null { + return this.modelsFormGroup.valid ? null : { + modelsFormGroup: false + }; } - private createObjectsLwM2M = (objectsLwM2M: ObjectLwM2M[]): FormArray => { - return this.fb.array(objectsLwM2M.map((objectLwM2M) => { - return this.fb.group({ - keyId: objectLwM2M.keyId, - name: objectLwM2M.name, - multiple: objectLwM2M.multiple, - mandatory: objectLwM2M.mandatory, - attributeLwm2m: objectLwM2M.attributeLwm2m, - instances: this.createInstanceLwM2M(objectLwM2M.instances) - }); - })); + get modelsFormArray(): FormArray { + return this.modelsFormGroup.get('models') as FormArray; } - private createInstanceLwM2M = (instancesLwM2M: Instance[]): FormArray => { - return this.fb.array(instancesLwM2M.map((instanceLwM2M) => { - return this.fb.group({ - id: instanceLwM2M.id, - attributeLwm2m: {value: instanceLwM2M.attributeLwm2m, disabled: this.disabled}, - resources: {value: instanceLwM2M.resources, disabled: this.disabled} + private updateModels(models: ObjectLwM2M[]) { + if (models.length === this.modelsFormArray.length) { + this.modelsFormArray.patchValue(models, {emitEvent: false}); + } else { + if (this.valueChange$) { + this.valueChange$.unsubscribe(); + } + const modelControls: Array = []; + models.forEach(model => { + modelControls.push(this.createModelFormGroup(model)); }); - })); - } - - get clientLwM2MFormArray(): FormArray { - return this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray; - } - - instancesLwm2mFormArray = (objectLwM2M: AbstractControl): FormArray => { - return objectLwM2M.get(INSTANCES) as FormArray; - } - - changeInstanceResourcesCheckBox = (value: boolean, instance: AbstractControl, type: string): void => { - const resources = deepClone(instance.get(RESOURCES).value as ResourceLwM2M[]); - resources.forEach(resource => resource[type] = value); - instance.get(RESOURCES).patchValue(resources); - this.propagateChange(this.observeAttrTelemetryFormGroup.value); - } - - private updateValidators = (): void => { - this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M).setValidators(this.required ? Validators.required : []); - this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M).updateValueAndValidity(); + this.modelsFormGroup.setControl('models', this.fb.array(modelControls)); + if (this.disabled) { + this.modelsFormGroup.disable({emitEvent: false}); + } + this.valueChange$ = this.modelsFormGroup.valueChanges.subscribe(value => { + this.updateModel(value.models); + }); + } } - trackByParams = (index: number, element: any): number => { - return index; + private createModelFormGroup(objectLwM2M: ObjectLwM2M): FormGroup { + return this.fb.group({ + id: [objectLwM2M.id], + keyId: [objectLwM2M.keyId], + name: [objectLwM2M.name], + multiple: [objectLwM2M.multiple], + mandatory: [objectLwM2M.mandatory], + attributes: [objectLwM2M.attributes], + instances: [objectLwM2M.instances] + }); } - getIndeterminate = (instance: AbstractControl, type: string): boolean => { - const resources = instance.get(RESOURCES).value as ResourceLwM2M[]; - if (isDefinedAndNotNull(resources)) { - const checkedResource = resources.filter(resource => resource[type]); - return checkedResource.length !== 0 && checkedResource.length !== resources.length; + private updateModel = (value: ObjectLwM2M[]): void => { + if (value && this.modelsFormGroup.valid) { + this.propagateChange(value); + } else { + this.propagateChange(null); } - return false; } - getChecked = (instance: AbstractControl, type: string): boolean => { - const resources = instance.get(RESOURCES).value as ResourceLwM2M[]; - return isDefinedAndNotNull(resources) && resources.every(resource => resource[type]); + private updateValidators = (): void => { + this.modelsFormArray.setValidators(this.required ? Validators.required : []); + this.modelsFormArray.updateValueAndValidity(); } - getExpended = (objectLwM2M: AbstractControl): boolean => { - return this.instancesLwm2mFormArray(objectLwM2M).length === 1; + trackByParams = (index: number, objectLwM2M: ObjectLwM2M): number => { + return objectLwM2M.id; } /** @@ -230,137 +194,76 @@ export class Lwm2mObserveAttrTelemetryComponent implements ControlValueAccessor * Object Instance ID cnt_max = cnt_min = 1 (всегда есть один) */ - addInstances = ($event: Event, object: ObjectLwM2M): void => { + addInstances = ($event: Event, control: AbstractControl): void => { if ($event) { $event.stopPropagation(); $event.preventDefault(); } - this.dialog.open(Lwm2mObjectAddInstancesDialogComponent, { + const object: ObjectLwM2M = control.value; + const instancesId: Set = this.instancesToSetId(object.instances); + this.dialog.open(Lwm2mObjectAddInstancesDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], data: { - instancesIds: this.instancesToSetId(object.instances), + instancesId: new Set(instancesId), objectName: object.name, - objectKeyId: object.keyId + objectId: object.id } }).afterClosed().subscribe( - (res: Lwm2mObjectAddInstancesData | undefined) => { + (res: Set | null) => { if (isDefinedAndNotNull(res)) { - this.updateInstancesIds(res); + this.updateInstancesIds(res, control, instancesId); } } ); } - private updateInstancesIds = (data: Lwm2mObjectAddInstancesData): void => { - 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 []; - const instancesFormArray = objectLwM2MFormGroup.get(INSTANCES) as FormArray; - const instance0 = deepClone(instancesFormArray.at(0).value as Instance); - instance0.resources.forEach(r => { - r.attribute = false; - r.telemetry = false; - r.observe = false; - r.attributeLwm2m = {}; + private updateInstancesIds(instancesId: Set, control: AbstractControl, prevInstancesId: Set) { + let instancesValue: Instance[] = control.get('instances').value; + const instanceTemplate = deepClone(instancesValue[0]); + instanceTemplate.attributes = {}; + instanceTemplate.resources.forEach(resource => { + resource.attribute = false; + resource.telemetry = false; + resource.observe = false; + resource.keyName = _.camelCase(resource.name); + resource.attributes = {}; }); - const valueOld = this.instancesToSetId(instancesArray); - if (!isEqual(valueOld, data.instancesIds)) { - const idsDel = this.diffBetweenSet(valueOld, data.instancesIds); - const idsAdd = this.diffBetweenSet(data.instancesIds, valueOld); + if (!isEqual(prevInstancesId, instancesId)) { + const idsDel = this.diffBetweenSet(prevInstancesId, instancesId); + const idsAdd = this.diffBetweenSet(instancesId, prevInstancesId); if (idsAdd.size) { - this.addInstancesNew(idsAdd, objectLwM2MFormGroup, instancesFormArray, instance0); + idsAdd.forEach(id => { + const template = deepClone(instanceTemplate); + template.resources.forEach(resource => resource.keyName += id); + template.id = id; + instancesValue.push(template); + }); } if (idsDel.size) { - this.deleteInstances(idsDel, objectLwM2MFormGroup, instancesFormArray, instance0); + instancesValue = instancesValue.filter(instance => !idsDel.has(instance.id)); + if (instancesValue.length === 0) { + instanceTemplate.id = 0; + instancesValue.push(instanceTemplate); + } + } + if (idsAdd.size || idsDel.size) { + instancesValue.sort((a, b) => a.id - b.id); + control.get('instances').patchValue(instancesValue); + this.cd.markForCheck(); } } } - private addInstancesNew = (idsAdd: Set, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray, - instanceNew: Instance): void => { - idsAdd.forEach(x => { - 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); - } - - private deleteInstances = (idsDel: Set, objectLwM2MFormGroup: FormGroup, instancesFormArray: FormArray, - instance0: Instance): void => { - idsDel.forEach(x => { - const instanceIndex = instancesFormArray.value.findIndex(element => element.id === x); - instancesFormArray.removeAt(instanceIndex); - }); - if (instancesFormArray.length === 0) { - this.pushInstance(instancesFormArray, 0, instance0); - } - (instancesFormArray.controls as FormGroup[]).sort((a, b) => a.value.id - b.value.id); - } - - private pushInstance = (instancesFormArray: FormArray, x: number, instanceNew: Instance): void => { - instancesFormArray.push(this.fb.group({ - id: x, - attributeLwm2m: instanceNew.attributeLwm2m, - resources: {value: instanceNew.resources, disabled: this.disabled} - })); - } - private diffBetweenSet(firstSet: Set, secondSet: Set): Set { return new Set([...Array.from(firstSet)].filter(x => !secondSet.has(x))); } - private instancesToSetId = (instances: Instance[]): Set => { + private instancesToSetId(instances: Instance[]): Set { return new Set(instances.map(x => x.id)); } - getNameObjectLwm2m = (objectName: string, idVerObj: string): string => { - return objectName + ' <' + idVerObj + '>'; - } - getNameInstanceLwm2m = (instance: Instance, idVerObj: string): string => { - return ' instance <' + idVerObj + '/' + instance.id +'>'; - } - - updateAttributeLwm2mObject = (event: Event, objectKeyId: number): void => { - const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls - .find(e => e.value.keyId === objectKeyId) as FormGroup; - objectLwM2MFormGroup.patchValue({attributeLwm2m: event}); - } - - updateAttributeLwm2mInstance = (event: Event, indexInstance: number, objectKeyId: number): void => { - - const objectLwM2MFormGroup = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).controls - .find(e => e.value.keyId === objectKeyId) as FormGroup; - const instancesFormArray = objectLwM2MFormGroup.get(INSTANCES) as FormArray; - instancesFormArray.at(indexInstance).patchValue({attributeLwm2m: event}); - } - - disableObserveInstance = (instance: AbstractControl): boolean => { - const checkedAttrTelemetry = this.observeInstance(instance); - if (checkedAttrTelemetry) { - instance.get(ATTRIBUTE_LWM2M).patchValue(null); - } - return checkedAttrTelemetry; - } - - disableObserveObject = (index: number): boolean => { - const object = (this.observeAttrTelemetryFormGroup.get(CLIENT_LWM2M) as FormArray).at(index) as FormGroup; - const instances = object.controls.instances as FormArray; - const checkedAttrTelemetry = instances.controls.filter(instance => !this.disableObserveInstance(instance)); - if (checkedAttrTelemetry.length === 0) { - object.controls.attributeLwm2m.patchValue(null); - } - return checkedAttrTelemetry.length === 0; - } - - - observeInstance = (instance: AbstractControl): boolean => { - const resources = instance.get(RESOURCES).value as ResourceLwM2M[]; - if (isDefinedAndNotNull(resources)) { - const checkedAttribute = resources.filter(resource => resource[ATTRIBUTE]); - const checkedTelemetry = resources.filter(resource => resource[TELEMETRY]); - return checkedAttribute.length === 0 && checkedTelemetry.length === 0; - } - return false; + getNameObject = (objectLwM2M: ObjectLwM2M): string => { + return `${objectLwM2M.name} <${objectLwM2M.id}>`; } } 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 4575d2bfff..6be30b3fc8 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 @@ -18,7 +18,7 @@ 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 { Lwm2mObserveAttrTelemetryResourcesComponent } from './lwm2m-observe-attr-telemetry-resources.component'; import { Lwm2mAttributesDialogComponent } from './lwm2m-attributes-dialog.component'; import { Lwm2mAttributesComponent } from './lwm2m-attributes.component'; import { Lwm2mAttributesKeyListComponent } from './lwm2m-attributes-key-list.component'; @@ -27,6 +27,7 @@ import { Lwm2mObjectAddInstancesDialogComponent } from './lwm2m-object-add-insta import { Lwm2mObjectAddInstancesListComponent } from './lwm2m-object-add-instances-list.component'; import { CommonModule } from '@angular/common'; import { SharedModule } from '@app/shared/shared.module'; +import { Lwm2mObserveAttrTelemetryInstancesComponent } from './lwm2m-observe-attr-telemetry-instances.component'; @NgModule({ declarations: @@ -34,13 +35,14 @@ import { SharedModule } from '@app/shared/shared.module'; Lwm2mDeviceProfileTransportConfigurationComponent, Lwm2mObjectListComponent, Lwm2mObserveAttrTelemetryComponent, - Lwm2mObserveAttrTelemetryResourceComponent, + Lwm2mObserveAttrTelemetryResourcesComponent, Lwm2mAttributesDialogComponent, Lwm2mAttributesComponent, Lwm2mAttributesKeyListComponent, Lwm2mDeviceConfigServerComponent, Lwm2mObjectAddInstancesDialogComponent, - Lwm2mObjectAddInstancesListComponent + Lwm2mObjectAddInstancesListComponent, + Lwm2mObserveAttrTelemetryInstancesComponent ], imports: [ CommonModule, @@ -50,13 +52,14 @@ import { SharedModule } from '@app/shared/shared.module'; Lwm2mDeviceProfileTransportConfigurationComponent, Lwm2mObjectListComponent, Lwm2mObserveAttrTelemetryComponent, - Lwm2mObserveAttrTelemetryResourceComponent, + Lwm2mObserveAttrTelemetryResourcesComponent, Lwm2mAttributesDialogComponent, Lwm2mAttributesComponent, Lwm2mAttributesKeyListComponent, Lwm2mDeviceConfigServerComponent, Lwm2mObjectAddInstancesDialogComponent, - Lwm2mObjectAddInstancesListComponent + Lwm2mObjectAddInstancesListComponent, + Lwm2mObserveAttrTelemetryInstancesComponent ], providers: [ ] 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 0f419457f0..bc48182e9d 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 @@ -20,8 +20,6 @@ export const PAGE_SIZE_LIMIT = 50; export const INSTANCES = 'instances'; export const INSTANCE = 'instance'; export const RESOURCES = 'resources'; -export const ATTRIBUTE_LWM2M = 'attributeLwm2m'; -export const CLIENT_LWM2M = 'clientLwM2M'; export const OBSERVE_ATTR_TELEMETRY = 'observeAttrTelemetry'; export const OBSERVE = 'observe'; export const ATTRIBUTE = 'attribute'; @@ -184,7 +182,7 @@ export interface ObservableAttributes { attribute: string[]; telemetry: string[]; keyName: {}; - attributeLwm2m?: AttributesNameValueMap; + attributeLwm2m?: AttributesNameValueMap[]; } export function getDefaultBootstrapServersSecurityConfig(): BootstrapServersSecurityConfig { @@ -222,7 +220,7 @@ export function getDefaultProfileObserveAttrConfig(): ObservableAttributes { attribute: [], telemetry: [], keyName: {}, - attributeLwm2m: {} + attributeLwm2m: [] }; } @@ -237,6 +235,8 @@ export function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSetting }; } +export type ResourceSettingTelemetry = 'observe' | 'attribute' | 'telemetry'; + export interface ResourceLwM2M { id: number; name: string; @@ -244,12 +244,12 @@ export interface ResourceLwM2M { attribute: boolean; telemetry: boolean; keyName: string; - attributeLwm2m?: AttributesNameValueMap; + attributes?: AttributesNameValueMap; } export interface Instance { id: number; - attributeLwm2m?: AttributesNameValueMap; + attributes?: AttributesNameValueMap; resources: ResourceLwM2M[]; } @@ -265,7 +265,7 @@ export interface ObjectLwM2M { name: string; multiple?: boolean; mandatory?: boolean; - attributeLwm2m?: AttributesNameValueMap; + attributes?: AttributesNameValueMap; instances?: Instance []; } 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 6cff8b93cf..89c31a70c2 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1228,25 +1228,20 @@ "valid-id-instance-no-max": "Instance number '{{instance}}' no validated. Max value='{{max}}'", "valid-id-instance": "Instance number '{{instance}}' no validated. { count, plural, 1 {Max value='{{max}}'} 2 {Min value='{{min}}'} other {Must be only number} }", "model-tab": "LWM2M Model", - "add-instances-tip": "Add new instances", + "add-new-instances": "Add new instances", "instances-list": "Instances list", "instances-input": "Input Instance Id value", "instances-input-holder": "Input Instance number...", - "instance-label": "Instance", + "instance": "Instance", "resource-label": " Resource name", "observe-label": "Observe", "attribute-label": "Attribute", "telemetry-label": "Telemetry", - "key-name-label": "Key Name", - "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", - "is-telemetry-tip": "Is Telemetry", - "key-name-tip": "Key Name in Camel format", - "edit-attributes-select": "To edit attributes select telemetry or attributes", + "edit-observe-select": "To edit observe select telemetry or attribute", + "edit-attributes-select": "To edit attributes select telemetry or attribute", "no-attributes-set": "No attributes set", - "key-name": "Key Name", + "key-name": "Key name", + "key-name-required": "Key name is required", "attribute-name": "Name attribute", "attribute-name-required": "Name attribute is required.", "attribute-value": "Attribute value", From 6bc5f7575da47a91d78436dfaa625069f0418910 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 29 Jun 2021 17:26:37 +0300 Subject: [PATCH 174/231] Composite Operations parameter --- .../common/data/device/data/lwm2m/OtherConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 81d9d56a8c..c940cd5e85 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 @@ -27,6 +27,6 @@ public class OtherConfiguration { private PowerMode powerMode; private String fwUpdateResource; private String swUpdateResource; - private boolean composite; + private boolean compositeOperationsSupport; } From 393d4227847a156f7810607d3d4c490d551b1707 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 29 Jun 2021 20:06:34 +0300 Subject: [PATCH 175/231] fetch all clients after startUp --- .../device/DeviceActorMessageProcessor.java | 4 +- .../lwm2m/server/LwM2mTransportUtil.java | 12 ------ .../DefaultLwM2MAttributesService.java | 3 -- .../lwm2m/server/client/LwM2mClient.java | 2 + .../server/client/LwM2mClientContextImpl.java | 41 +++++++++++++------ .../server/store/TbDummyLwM2MClientStore.java | 8 ++++ .../server/store/TbLwM2MClientStore.java | 4 ++ .../server/store/TbLwM2mSecurityStore.java | 3 -- .../server/store/TbRedisLwM2MClientStore.java | 32 +++++++++++++++ 9 files changed, 77 insertions(+), 32 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 2d5f107d61..4d41b258f3 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 @@ -269,7 +269,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { ToDeviceRpcRequestMetadata requestMd = toDeviceRpcPendingMap.remove(msg.getId()); if (requestMd != null) { log.debug("[{}] RPC request [{}] timeout detected!", deviceId, msg.getId()); - systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.TIMEOUT, null); + if (requestMd.getMsg().getMsg().isPersisted()) { + systemContext.getTbRpcService().save(tenantId, new RpcId(requestMd.getMsg().getMsg().getId()), RpcStatus.TIMEOUT, null); + } systemContext.getTbCoreDeviceRpcService().processRpcResponseFromDeviceActor(new FromDeviceRpcResponse(requestMd.getMsg().getMsg().getId(), null, requestMd.isSent() ? RpcError.TIMEOUT : RpcError.NO_ACTIVE_CONNECTION)); } 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 923c9d7bad..38f3877cb7 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 @@ -16,34 +16,24 @@ package org.thingsboard.server.transport.lwm2m.server; import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import com.google.gson.JsonSyntaxException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; 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.LwM2mNode; -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.node.codec.CodecException; import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.registration.Registration; -import org.nustaq.serialization.FSTConfiguration; import org.thingsboard.server.common.data.DeviceProfile; 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.transport.TransportServiceCallback; import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.client.ResourceValue; @@ -56,10 +46,8 @@ import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMs import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.LinkedList; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; 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 0f0d95610b..3bf6883e7b 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 @@ -23,9 +23,6 @@ import org.eclipse.leshan.core.model.ResourceModel; import org.eclipse.leshan.core.node.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.ota.OtaPackageKey; -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; import org.thingsboard.server.gen.transport.TransportProtos; 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 8cf6f20f36..41e4b88f28 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 @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.lwm2m.server.client; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; @@ -60,6 +61,7 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.f import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.getVerFromPathIdVerOrId; @Slf4j +@EqualsAndHashCode(of = {"endpoint"}) public class LwM2mClient implements Serializable { private static final long serialVersionUID = 8793482946289222623L; 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 261220f013..65028a599a 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,9 +26,9 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; -import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; @@ -68,6 +68,17 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { private final Map lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); private final Map profiles = new ConcurrentHashMap<>(); + @AfterStartUp + public void init() { + String nodeId = context.getNodeId(); + Set fetchedClients = clientStore.getAll(); + log.debug("Fetched clients from store: {}", fetchedClients); + fetchedClients.forEach(client -> { + lwM2mClientsByEndpoint.put(client.getEndpoint(), client); + updateFetchedClient(nodeId, client); + }); + } + @Override public LwM2mClient getClientByEndpoint(String endpoint) { return lwM2mClientsByEndpoint.computeIfAbsent(endpoint, ep -> { @@ -78,23 +89,27 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { client = new LwM2mClient(nodeId, ep); } else { log.debug("[{}] fetched client from store: {}", endpoint, client); - boolean updated = false; - if (client.getRegistration() != null) { - lwM2mClientsByRegistrationId.put(client.getRegistration().getId(), client); - } - if (client.getSession() != null) { - client.refreshSessionId(nodeId); - sessionManager.register(client.getSession()); - updated = true; - } - if (updated) { - clientStore.put(client); - } + updateFetchedClient(nodeId, client); } return client; }); } + private void updateFetchedClient(String nodeId, LwM2mClient client) { + boolean updated = false; + if (client.getRegistration() != null) { + lwM2mClientsByRegistrationId.put(client.getRegistration().getId(), client); + } + if (client.getSession() != null) { + client.refreshSessionId(nodeId); + sessionManager.register(client.getSession()); + updated = true; + } + if (updated) { + clientStore.put(client); + } + } + @Override public Optional register(LwM2mClient client, Registration registration) throws LwM2MClientStateException { TransportProtos.SessionInfoProto oldSession = null; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MClientStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MClientStore.java index 3708bf4b0e..767ab43493 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MClientStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MClientStore.java @@ -17,12 +17,20 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import java.util.Collections; +import java.util.Set; + public class TbDummyLwM2MClientStore implements TbLwM2MClientStore { @Override public LwM2mClient get(String endpoint) { return null; } + @Override + public Set getAll() { + return Collections.emptySet(); + } + @Override public void put(LwM2mClient client) { diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientStore.java index 16e4e9e90a..55b0423b05 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientStore.java @@ -17,10 +17,14 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import java.util.Set; + public interface TbLwM2MClientStore { LwM2mClient get(String endpoint); + Set getAll(); + void put(LwM2mClient client); void remove(String 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 bf1f275f32..ac0d3d0e68 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 @@ -23,12 +23,9 @@ import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInf 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; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java index a408cc22c4..d735eed26e 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbRedisLwM2MClientStore.java @@ -16,9 +16,17 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.nustaq.serialization.FSTConfiguration; +import org.springframework.data.redis.connection.RedisClusterConnection; import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.Cursor; +import org.springframework.data.redis.core.ScanOptions; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + public class TbRedisLwM2MClientStore implements TbLwM2MClientStore { private static final String CLIENT_EP = "CLIENT#EP#"; @@ -42,6 +50,30 @@ public class TbRedisLwM2MClientStore implements TbLwM2MClientStore { } } + @Override + public Set getAll() { + try (var connection = connectionFactory.getConnection()) { + Set clients = new HashSet<>(); + ScanOptions scanOptions = ScanOptions.scanOptions().count(100).match(CLIENT_EP + "*").build(); + List> scans = new ArrayList<>(); + if (connection instanceof RedisClusterConnection) { + ((RedisClusterConnection) connection).clusterGetNodes().forEach(node -> { + scans.add(((RedisClusterConnection) connection).scan(node, scanOptions)); + }); + } else { + scans.add(connection.scan(scanOptions)); + } + + scans.forEach(scan -> { + scan.forEachRemaining(key -> { + byte[] element = connection.get(key); + clients.add((LwM2mClient) serializer.asObject(element)); + }); + }); + return clients; + } + } + @Override public void put(LwM2mClient client) { byte[] clientSerialized = serializer.asByteArray(client); From 6cbf5878821b92ff9e38a15cf920763c0ef36360 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 30 Jun 2021 11:11:43 +0300 Subject: [PATCH 176/231] UI: Image map revert setting: Disable scroll zooming --- .../app/modules/home/components/widget/lib/maps/schemes.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts index b00e01404a..0287cb17d8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts @@ -413,10 +413,7 @@ export const commonMapSettingsSchema = }, 'mapPageSize', 'draggableMarker', - { - key: 'disableScrollZooming', - condition: 'model.provider !== "image-map"' - }, + 'disableScrollZooming', { key: 'latKeyName', condition: 'model.provider !== "image-map"' From d8ee95507925f76cc772b3b49b55c54d61766800 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 29 Jun 2021 17:55:40 +0300 Subject: [PATCH 177/231] implemented sendMsgsAfterSleeping --- .../server/client/LwM2mClientContext.java | 2 + .../server/client/LwM2mClientContextImpl.java | 38 ++++++++++++++++++- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 19 ---------- 3 files changed, 39 insertions(+), 20 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 8b8186f3db..a7305177e1 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 @@ -58,4 +58,6 @@ public interface LwM2mClientContext { void update(LwM2mClient lwM2MClient); void removeCredentials(TransportProtos.SessionInfoProto sessionInfo); + + void sendMsgsAfterSleeping(LwM2mClient lwM2MClient); } 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 261220f013..3802ef8234 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,12 +21,15 @@ 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; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.device.data.PowerMode; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; 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.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; @@ -34,9 +37,11 @@ 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.ota.LwM2MOtaUpdateService; import org.thingsboard.server.transport.lwm2m.server.session.LwM2MSessionManager; import org.thingsboard.server.transport.lwm2m.server.store.TbLwM2MClientStore; import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore; +import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; import java.util.Arrays; import java.util.Collection; @@ -64,6 +69,14 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { private final TbLwM2MClientStore clientStore; private final LwM2MSessionManager sessionManager; private final TransportDeviceProfileCache deviceProfileCache; + + @Autowired + @Lazy + private DefaultLwM2MUplinkMsgHandler defaultLwM2MUplinkMsgHandler; + @Autowired + @Lazy + private LwM2MOtaUpdateService otaUpdateService; + private final Map lwM2mClientsByEndpoint = new ConcurrentHashMap<>(); private final Map lwM2mClientsByRegistrationId = new ConcurrentHashMap<>(); private final Map profiles = new ConcurrentHashMap<>(); @@ -143,6 +156,7 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { } client.setRegistration(registration); clientStore.put(client); + sendMsgsAfterSleeping(client); } finally { client.unlock(); } @@ -233,6 +247,28 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { //TODO: implement } + @Override + public void sendMsgsAfterSleeping(LwM2mClient lwM2MClient) { + if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) { + PowerMode powerMode = lwM2MClient.getPowerMode(); + if (powerMode == null) { + Lwm2mDeviceProfileTransportConfiguration deviceProfile = getProfile(lwM2MClient.getProfileId()); + powerMode = deviceProfile.getClientLwM2mSettings().getPowerMode(); + } + + if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) { + defaultLwM2MUplinkMsgHandler.initAttributes(lwM2MClient); + TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg + .newBuilder() + .setSessionInfo(lwM2MClient.getSession()) + .setSendPendingRPC(TransportProtos.SendPendingRPCMsg.newBuilder().build()) + .build(); + context.getTransportService().process(persistentRpcRequestMsg, TransportServiceCallback.EMPTY); + otaUpdateService.init(lwM2MClient); + } + } + } + @Override public Collection getLwM2mClients() { return lwM2mClientsByEndpoint.values(); 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 cd8b19c60a..9bdf53741d 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 @@ -42,7 +42,6 @@ import org.thingsboard.common.util.DonAsynchron; 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.PowerMode; 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; @@ -390,24 +389,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); logService.log(lwM2MClient, LOG_LWM2M_INFO + ": Client is awake!"); - - if (LwM2MClientState.REGISTERED.equals(lwM2MClient.getState())) { - PowerMode powerMode = lwM2MClient.getPowerMode(); - if (powerMode == null) { - Lwm2mDeviceProfileTransportConfiguration deviceProfile = clientContext.getProfile(lwM2MClient.getProfileId()); - powerMode = deviceProfile.getClientLwM2mSettings().getPowerMode(); - } - - if (PowerMode.PSM.equals(powerMode) || PowerMode.E_DRX.equals(powerMode)) { - initAttributes(lwM2MClient); - TransportProtos.TransportToDeviceActorMsg persistentRpcRequestMsg = TransportProtos.TransportToDeviceActorMsg - .newBuilder() - .setSessionInfo(lwM2MClient.getSession()) - .setSendPendingRPC(TransportProtos.SendPendingRPCMsg.newBuilder().build()) - .build(); - transportService.process(persistentRpcRequestMsg, TransportServiceCallback.EMPTY); - } - } } /** From 290074ffcdd10bcc534f3ebcd278e0c7bc8dbba8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 30 Jun 2021 11:53:00 +0300 Subject: [PATCH 178/231] replay downlink on device awake --- .../lwm2m/server/uplink/DefaultLwM2MUplinkMsgHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 9bdf53741d..1850212b6e 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 @@ -387,8 +387,9 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl @Override public void onAwakeDev(Registration registration) { log.trace("[{}] [{}] Received endpoint Awake version event", registration.getId(), registration.getEndpoint()); - LwM2mClient lwM2MClient = this.clientContext.getClientByEndpoint(registration.getEndpoint()); - logService.log(lwM2MClient, LOG_LWM2M_INFO + ": Client is awake!"); + LwM2mClient client = this.clientContext.getClientByEndpoint(registration.getEndpoint()); + logService.log(client, LOG_LWM2M_INFO + ": Client is awake!"); + clientContext.sendMsgsAfterSleeping(client); } /** From e1c1e7ebbcb2d801b645488af4e474e3e4af08a6 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 30 Jun 2021 15:30:52 +0300 Subject: [PATCH 179/231] cache: CachedAttributesService fixed after merge to the latest master, fixed license headers --- .../attributes/CachedAttributesService.java | 38 +++++++++++-- .../dao/cache/CacheExecutorService.java | 35 ++++-------- .../CachedAttributesServiceTest.java | 55 ++++++------------- 3 files changed, 59 insertions(+), 69 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 d4e29f02de..ef0e580e34 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 @@ -19,10 +19,12 @@ 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.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.Cache; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EntityId; @@ -34,6 +36,7 @@ import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.cache.CacheExecutorService; import org.thingsboard.server.dao.service.Validator; +import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -43,6 +46,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.Executor; import java.util.stream.Collectors; import static org.thingsboard.server.dao.attributes.AttributeUtils.validate; @@ -53,12 +57,17 @@ import static org.thingsboard.server.dao.attributes.AttributeUtils.validate; @Slf4j public class CachedAttributesService implements AttributesService { private static final String STATS_NAME = "attributes.cache"; + public static final String LOCAL_CACHE_TYPE = "caffeine"; private final AttributesDao attributesDao; private final AttributesCacheWrapper cacheWrapper; + private final CacheExecutorService cacheExecutorService; private final DefaultCounter hitCounter; private final DefaultCounter missCounter; - private final CacheExecutorService cacheExecutorService; + private Executor cacheExecutor; + + @Value("${cache.type}") + private String cacheType; public CachedAttributesService(AttributesDao attributesDao, AttributesCacheWrapper cacheWrapper, @@ -72,6 +81,25 @@ public class CachedAttributesService implements AttributesService { this.missCounter = statsFactory.createDefaultCounter(STATS_NAME, "result", "miss"); } + @PostConstruct + public void init() { + this.cacheExecutor = getExecutor(cacheType, cacheExecutorService); + } + + /** + * Will return: + * - for the local cache type (cache.type="coffeine"): directExecutor (run callback immediately in the same thread) + * - for the remote cache: dedicated thread pool for the cache IO calls to unblock any caller thread + * */ + Executor getExecutor(String cacheType, CacheExecutorService cacheExecutorService) { + if (StringUtils.isEmpty(cacheType) || LOCAL_CACHE_TYPE.equals(cacheType)) { + log.info("Going to use directExecutor for the local cache type {}", cacheType); + return MoreExecutors.directExecutor(); + } + log.info("Going to use cacheExecutorService for the remote cache type {}", cacheType); + return cacheExecutorService; + } + @Override public ListenableFuture> find(TenantId tenantId, EntityId entityId, String scope, String attributeKey) { validate(entityId, scope); @@ -90,7 +118,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; - }, cacheExecutorService); + }, cacheExecutor); } } @@ -113,7 +141,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), cacheExecutorService); + return Futures.transform(result, foundInDbAttributes -> mergeDbAndCacheAttributes(entityId, scope, cachedAttributes, notFoundAttributeKeys, foundInDbAttributes), cacheExecutor); } @@ -171,7 +199,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), cacheExecutorService); + future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), cacheExecutor); return future; } @@ -179,7 +207,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), cacheExecutorService); + future.addListener(() -> evictAttributesFromCache(tenantId, entityId, scope, attributeKeys), cacheExecutor); return future; } 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; 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 index af980a6301..6a3429e5fb 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java @@ -1,38 +1,22 @@ /** - * 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.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; @@ -58,15 +42,11 @@ public class CachedAttributesServiceTest { assertThat(cachedAttributesService.getExecutor(null, cacheExecutorService), is(MoreExecutors.directExecutor())); - CacheConfiguration cacheConfiguration = new CacheConfiguration(); - cacheConfiguration.setType(null); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); + assertThat(cachedAttributesService.getExecutor((String) null, cacheExecutorService), is(MoreExecutors.directExecutor())); - cacheConfiguration.setType(""); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); + assertThat(cachedAttributesService.getExecutor("", cacheExecutorService), is(MoreExecutors.directExecutor())); - cacheConfiguration.setType(CachedAttributesService.LOCAL_CACHE_TYPE); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(MoreExecutors.directExecutor())); + assertThat(cachedAttributesService.getExecutor(CachedAttributesService.LOCAL_CACHE_TYPE, cacheExecutorService), is(MoreExecutors.directExecutor())); } @@ -74,14 +54,11 @@ public class CachedAttributesServiceTest { 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)); + willCallRealMethod().given(cachedAttributesService).getExecutor(any(String.class), any(CacheExecutorService.class)); - CacheConfiguration cacheConfiguration = new CacheConfiguration(); - cacheConfiguration.setType(REDIS); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(cacheExecutorService)); + assertThat(cachedAttributesService.getExecutor(REDIS, cacheExecutorService), is(cacheExecutorService)); - cacheConfiguration.setType("unknownCacheType"); - assertThat(cachedAttributesService.getExecutor(cacheConfiguration, cacheExecutorService), is(cacheExecutorService)); + assertThat(cachedAttributesService.getExecutor("unknownCacheType", cacheExecutorService), is(cacheExecutorService)); } From 6b622ce44bbb6a3e5b015e2b3dc52d915e4bbba9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 30 Jun 2021 15:40:47 +0300 Subject: [PATCH 180/231] cache: CachedAttributesService tiny test refactor --- .../server/dao/attributes/CachedAttributesServiceTest.java | 2 -- 1 file changed, 2 deletions(-) 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 index 6a3429e5fb..fc750c5a85 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/attributes/CachedAttributesServiceTest.java @@ -42,8 +42,6 @@ public class CachedAttributesServiceTest { assertThat(cachedAttributesService.getExecutor(null, cacheExecutorService), is(MoreExecutors.directExecutor())); - assertThat(cachedAttributesService.getExecutor((String) null, cacheExecutorService), is(MoreExecutors.directExecutor())); - assertThat(cachedAttributesService.getExecutor("", cacheExecutorService), is(MoreExecutors.directExecutor())); assertThat(cachedAttributesService.getExecutor(CachedAttributesService.LOCAL_CACHE_TYPE, cacheExecutorService), is(MoreExecutors.directExecutor())); From eb9b0f8433235f2ef675ab22e47d4563cc0d853c Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 30 Jun 2021 15:42:25 +0300 Subject: [PATCH 181/231] Implementation of software management --- application/src/main/resources/logback.xml | 1 - .../lwm2m/secure/TbLwM2MAuthorizer.java | 2 +- .../server/LwM2mTransportCoapResource.java | 18 +- .../lwm2m/server/LwM2mTransportUtil.java | 8 +- .../DefaultLwM2MAttributesService.java | 7 +- .../ota/DefaultLwM2MOtaUpdateService.java | 313 +++++++++++++----- .../lwm2m/server/ota/LwM2MClientOtaInfo.java | 72 ++-- .../server/ota/LwM2MOtaUpdateService.java | 16 +- .../ota/firmware/LwM2MClientFwOtaInfo.java | 56 ++++ .../ota/software/LwM2MClientSwOtaInfo.java | 57 ++++ .../ota/software/SoftwareUpdateResult.java | 3 + .../rpc/DefaultLwM2MRpcRequestHandler.java | 11 +- .../store/TbDummyLwM2MClientOtaInfoStore.java | 18 +- .../store/TbLwM2MClientOtaInfoStore.java | 10 +- .../store/TbLwM2mRedisClientOtaInfoStore.java | 34 +- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 40 +-- 16 files changed, 480 insertions(+), 186 deletions(-) create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MClientFwOtaInfo.java create mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml index 5f2a47f7ba..caf78e1537 100644 --- a/application/src/main/resources/logback.xml +++ b/application/src/main/resources/logback.xml @@ -26,7 +26,6 @@ - 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 5a94a7e3ed..04c6a2b160 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 @@ -62,7 +62,7 @@ public class TbLwM2MAuthorizer implements Authorizer { try { expectedSecurityInfo = securityStore.getByEndpoint(registration.getEndpoint()); } catch (LwM2MAuthException e) { - log.warn("Registration failed: FORBIDDEN, endpointId: [{}]", registration.getEndpoint()); + log.info("Registration failed: FORBIDDEN, endpointId: [{}]", registration.getEndpoint()); return null; } } 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 c8443415cc..bbeda58270 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 @@ -26,6 +26,7 @@ import org.eclipse.californium.core.server.resources.Resource; import org.eclipse.californium.core.server.resources.ResourceObserver; import org.thingsboard.server.cache.ota.OtaPackageDataCache; +import java.util.List; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -69,17 +70,18 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { @Override protected void processHandleGet(CoapExchange exchange) { - log.warn("90) processHandleGet [{}]", exchange); - if (exchange.getRequestOptions().getUriPath().size() >= 2 && - (FIRMWARE_UPDATE_COAP_RESOURCE.equals(exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size() - 2)) || - SOFTWARE_UPDATE_COAP_RESOURCE.equals(exchange.getRequestOptions().getUriPath().get(exchange.getRequestOptions().getUriPath().size() - 2)))) { + log.debug("processHandleGet [{}]", exchange); + List uriPath = exchange.getRequestOptions().getUriPath(); + if (uriPath.size() >= 2 && + (FIRMWARE_UPDATE_COAP_RESOURCE.equals(uriPath.get(uriPath.size() - 2)) || + SOFTWARE_UPDATE_COAP_RESOURCE.equals(uriPath.get(uriPath.size() - 2)))) { this.sendOtaData(exchange); } } @Override protected void processHandlePost(CoapExchange exchange) { - log.warn("2) processHandleGet [{}]", exchange); + log.debug("processHandlePost [{}]", exchange); } /** @@ -136,16 +138,16 @@ public class LwM2mTransportCoapResource extends AbstractLwM2mTransportResource { UUID currentId = UUID.fromString(idStr); Response response = new Response(CoAP.ResponseCode.CONTENT); byte[] fwData = this.getOtaData(currentId); - log.warn("91) read softWare data (length): [{}]", fwData.length); + log.debug("Read softWare data (length): [{}]", fwData.length); if (fwData != null && fwData.length > 0) { response.setPayload(fwData); if (exchange.getRequestOptions().getBlock2() != null) { int chunkSize = exchange.getRequestOptions().getBlock2().getSzx(); 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); + log.trace("With block2 Send currentId: [{}], length: [{}], chunkSize [{}], moreFlag [{}]", currentId.toString(), fwData.length, chunkSize, lastFlag); } else { - log.warn("92) with block1 Send currentId: [{}], length: [{}], ", currentId.toString(), fwData.length); + log.trace("With block1 Send currentId: [{}], length: [{}], ", currentId.toString(), fwData.length); } exchange.respond(response); } 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 38f3877cb7..5930825894 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 @@ -69,7 +69,7 @@ import static org.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPA 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; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_STATE_ID; @Slf4j public class LwM2mTransportUtil { @@ -143,7 +143,7 @@ public class LwM2mTransportUtil { lwM2mOtaConvert.setCurrentType(STRING); lwM2mOtaConvert.setValue(FirmwareUpdateResult.fromUpdateResultFwByCode(((Long) value).intValue()).getType()); return lwM2mOtaConvert; - } else if (SW_UPDATE_STATE_ID.equals(path)) { + } else if (SW_STATE_ID.equals(path)) { lwM2mOtaConvert.setCurrentType(STRING); lwM2mOtaConvert.setValue(SoftwareUpdateState.fromUpdateStateSwByCode(((Long) value).intValue()).type); return lwM2mOtaConvert; @@ -163,7 +163,7 @@ public class LwM2mTransportUtil { if (transportConfiguration.getType().equals(DeviceTransportType.LWM2M)) { return (Lwm2mDeviceProfileTransportConfiguration) transportConfiguration; } else { - log.warn("[{}] Received profile with invalid transport configuration: {}", deviceProfile.getId(), deviceProfile.getProfileData().getTransportConfiguration()); + log.info("[{}] Received profile with invalid transport configuration: {}", deviceProfile.getId(), deviceProfile.getProfileData().getTransportConfiguration()); throw new IllegalArgumentException("Received profile with invalid transport configuration: " + transportConfiguration.getType()); } } @@ -185,7 +185,7 @@ public class LwM2mTransportUtil { return pathIdVer; } } catch (Exception e) { - log.warn("Issue converting path with version [{}] to path without version: ", pathIdVer, e); + log.debug("Issue converting path with version [{}] to path without version: ", pathIdVer, e); throw new RuntimeException(e); } } 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 3bf6883e7b..353188c8ff 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 @@ -123,6 +123,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { String newFirmwareUrl = null; String newSoftwareTitle = null; String newSoftwareVersion = null; + String newSoftwareUrl = null; List otherAttributes = new ArrayList<>(); for (TransportProtos.TsKvProto tsKvProto : msg.getSharedUpdatedList()) { String attrName = tsKvProto.getKv().getKey(); @@ -136,7 +137,9 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { newSoftwareTitle = getStrValue(tsKvProto); } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_VERSION.equals(attrName)) { newSoftwareVersion = getStrValue(tsKvProto); - } else { + } else if (DefaultLwM2MOtaUpdateService.SOFTWARE_URL.equals(attrName)) { + newSoftwareUrl = getStrValue(tsKvProto); + }else { otherAttributes.add(tsKvProto); } } @@ -144,7 +147,7 @@ public class DefaultLwM2MAttributesService implements LwM2MAttributesService { otaUpdateService.onTargetFirmwareUpdate(lwM2MClient, newFirmwareTitle, newFirmwareVersion, Optional.ofNullable(newFirmwareUrl)); } if (newSoftwareTitle != null || newSoftwareVersion != null) { - otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion); + otaUpdateService.onTargetSoftwareUpdate(lwM2MClient, newSoftwareTitle, newSoftwareVersion, Optional.ofNullable(newSoftwareUrl)); } if (!otherAttributes.isEmpty()) { onAttributesUpdate(lwM2MClient, otherAttributes); 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 c6c2f4ea63..fd0fbf4a71 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 @@ -45,10 +45,12 @@ 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.LwM2MClientFwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MFirmwareUpdateStrategy; 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.LwM2MClientSwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MSoftwareUpdateStrategy; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateResult; import org.thingsboard.server.transport.lwm2m.server.ota.software.SoftwareUpdateState; @@ -91,12 +93,13 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl public static final String FIRMWARE_UPDATE_COAP_RESOURCE = "tbfw"; public static final String SOFTWARE_UPDATE_COAP_RESOURCE = "tbsw"; private static final String FW_PACKAGE_5_ID = "/5/0/0"; + private static final String FW_PACKAGE_19_ID = "/19/0/0"; private static final String FW_URL_ID = "/5/0/1"; private static final String FW_EXECUTE_ID = "/5/0/2"; 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"; + public static final String FW_VER_ID = "/5/0/7"; /** * Quectel@Hi15RM1-HLB_V1.0@BC68JAR01A10,V150R100C20B300SP7,V150R100C20B300SP7@8 * Revision:BC68JAR01A10 @@ -104,17 +107,19 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl 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_3_VER_ID = "/3/0/19"; + + public static final String SW_NAME_ID = "/9/0/0"; + public 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_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<>(); + private final Map fwStates = new ConcurrentHashMap<>(); + private final Map swStates = new ConcurrentHashMap<>(); private final TransportService transportService; private final LwM2mClientContext clientContext; @@ -155,27 +160,41 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl //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<>(); - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); if (fwInfo.isSupported()) { attributesToFetch.add(FIRMWARE_TITLE); attributesToFetch.add(FIRMWARE_VERSION); attributesToFetch.add(FIRMWARE_URL); } + LwM2MClientSwOtaInfo swInfo = getOrInitSwInfo(client); + if (swInfo.isSupported()) { + attributesToFetch.add(SOFTWARE_TITLE); + attributesToFetch.add(SOFTWARE_VERSION); + attributesToFetch.add(SOFTWARE_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); + Optional newFwTitle = getAttributeValue(attrs, FIRMWARE_TITLE); + Optional newFwVersion = getAttributeValue(attrs, FIRMWARE_VERSION); + Optional newFwUrl = getAttributeValue(attrs, FIRMWARE_URL); + if (newFwTitle.isPresent() && newFwVersion.isPresent()) { + onTargetFirmwareUpdate(client, newFwTitle.get(), newFwVersion.get(), newFwUrl); + } + } + if (swInfo.isSupported()) { + Optional newSwTitle = getAttributeValue(attrs, SOFTWARE_TITLE); + Optional newSwVersion = getAttributeValue(attrs, SOFTWARE_VERSION); + Optional newSwUrl = getAttributeValue(attrs, SOFTWARE_URL); + if (newSwTitle.isPresent() && newSwVersion.isPresent()) { + onTargetSoftwareUpdate(client, newSwTitle.get(), newSwVersion.get(), newSwUrl); } } }, throwable -> { if (fwInfo.isSupported()) { - fwInfo.setTargetFetchFailure(true); update(fwInfo); } }, executor); @@ -184,7 +203,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl @Override public void forceFirmwareUpdate(LwM2mClient client) { - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); fwInfo.setRetryAttempts(0); fwInfo.setFailedPackageId(null); startFirmwareUpdateIfNeeded(client, fwInfo); @@ -192,7 +211,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl @Override public void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional newFirmwareUrl) { - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); fwInfo.updateTarget(newFirmwareTitle, newFirmwareVersion, newFirmwareUrl); update(fwInfo); startFirmwareUpdateIfNeeded(client, fwInfo); @@ -201,15 +220,20 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl @Override public void onCurrentFirmwareNameUpdate(LwM2mClient client, String name) { log.debug("[{}] Current fw name: {}", client.getEndpoint(), name); - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); - fwInfo.setCurrentName(name); + getOrInitFwInfo(client).setCurrentName(name); + } + + @Override + public void onCurrentSoftwareNameUpdate(LwM2mClient client, String name) { + log.debug("[{}] Current sw name: {}", client.getEndpoint(), name); + getOrInitSwInfo(client).setCurrentName(name); } @Override public void onFirmwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) { log.debug("[{}] Current fw strategy: {}", client.getEndpoint(), configuration.getFwUpdateStrategy()); - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); - fwInfo.setFwStrategy(LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(configuration.getFwUpdateStrategy())); + LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setStrategy(LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(configuration.getFwUpdateStrategy())); fwInfo.setBaseUrl(configuration.getFwUpdateResource()); startFirmwareUpdateIfNeeded(client, fwInfo); } @@ -217,30 +241,30 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl @Override public void onCurrentSoftwareStrategyUpdate(LwM2mClient client, OtherConfiguration configuration) { log.debug("[{}] Current sw strategy: {}", client.getEndpoint(), configuration.getSwUpdateStrategy()); - LwM2MClientOtaInfo swInfo = getOrInitSwInfo(client); - swInfo.setSwStrategy(LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(configuration.getSwUpdateStrategy())); + LwM2MClientSwOtaInfo swInfo = getOrInitSwInfo(client); + swInfo.setStrategy(LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(configuration.getSwUpdateStrategy())); swInfo.setBaseUrl(configuration.getSwUpdateResource()); startSoftwareUpdateIfNeeded(client, swInfo); } @Override public void onCurrentFirmwareVersion3Update(LwM2mClient client, String version) { - log.debug("[{}] Current fw version: {}", client.getEndpoint(), version); - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + log.debug("[{}] Current fw version(3): {}", client.getEndpoint(), version); + LwM2MClientFwOtaInfo 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.setCurrentVersion5(version); + public void onCurrentFirmwareVersionUpdate(LwM2mClient client, String version) { + log.debug("[{}] Current fw version(5): {}", client.getEndpoint(), version); + LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); + fwInfo.setCurrentVersion(version); } @Override public void onCurrentFirmwareStateUpdate(LwM2mClient client, Long stateCode) { log.debug("[{}] Current fw state: {}", client.getEndpoint(), stateCode); - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); FirmwareUpdateState state = FirmwareUpdateState.fromStateFwByCode(stateCode.intValue()); if (FirmwareUpdateState.DOWNLOADED.equals(state)) { executeFwUpdate(client); @@ -255,7 +279,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl @Override public void onCurrentFirmwareResultUpdate(LwM2mClient client, Long code) { log.debug("[{}] Current fw result: {}", client.getEndpoint(), code); - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); FirmwareUpdateResult result = FirmwareUpdateResult.fromUpdateResultFwByCode(code.intValue()); Optional status = toOtaPackageUpdateStatus(result); status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, fwInfo, @@ -272,16 +296,66 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl @Override public void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient client, Long value) { log.debug("[{}] Current fw delivery method: {}", client.getEndpoint(), value); - LwM2MClientOtaInfo fwInfo = getOrInitFwInfo(client); + LwM2MClientFwOtaInfo fwInfo = getOrInitFwInfo(client); fwInfo.setDeliveryMethod(value.intValue()); } @Override - public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion) { + public void onCurrentSoftwareVersion3Update(LwM2mClient client, String version) { + log.debug("[{}] Current sw version(3): {}", client.getEndpoint(), version); + getOrInitSwInfo(client).setCurrentVersion3(version); + } + + @Override + public void onCurrentSoftwareVersionUpdate(LwM2mClient client, String version) { + log.debug("[{}] Current sw version(9): {}", client.getEndpoint(), version); + getOrInitSwInfo(client).setCurrentVersion(version); + } + @Override + public void onCurrentSoftwareStateUpdate(LwM2mClient client, Long stateCode) { + log.debug("[{}] Current sw state: {}", client.getEndpoint(), stateCode); + LwM2MClientSwOtaInfo swInfo = getOrInitSwInfo(client); + SoftwareUpdateState state = SoftwareUpdateState.fromUpdateStateSwByCode(stateCode.intValue()); + if (SoftwareUpdateState.INITIAL.equals(state)) { + startSoftwareUpdateIfNeeded(client, swInfo); + } else if (SoftwareUpdateState.DELIVERED.equals(state)) { + executeSwInstall(client); + } + swInfo.setUpdateState(state); + Optional status = toOtaPackageUpdateStatus(state); + status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, swInfo, + otaStatus, "Firmware Update State: " + state.name())); + update(swInfo); } - private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo fwInfo) { + + @Override + public void onCurrentSoftwareResultUpdate(LwM2mClient client, Long code) { + log.debug("[{}] Current sw result: {}", client.getEndpoint(), code); + LwM2MClientSwOtaInfo swInfo = getOrInitSwInfo(client); + SoftwareUpdateResult result = SoftwareUpdateResult.fromUpdateResultSwByCode(code.intValue()); + Optional status = toOtaPackageUpdateStatus(result); + status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, swInfo, + otaStatus, "Firmware Update Result: " + result.name())); + if (result.isAgain() && swInfo.getRetryAttempts() <= 2) { + swInfo.setRetryAttempts(swInfo.getRetryAttempts() + 1); + startSoftwareUpdateIfNeeded(client, swInfo); + } else { + swInfo.update(result); + } + update(swInfo); + } + + @Override + public void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion, Optional newFirmwareUrl) { + LwM2MClientSwOtaInfo fwInfo = getOrInitSwInfo(client); + fwInfo.updateTarget(newSoftwareTitle, newSoftwareVersion, newFirmwareUrl); + update(fwInfo); + startSoftwareUpdateIfNeeded(client, fwInfo); + } + + private void startFirmwareUpdateIfNeeded(LwM2mClient client, LwM2MClientFwOtaInfo fwInfo) { try { if (!fwInfo.isSupported()) { log.debug("[{}] Fw update is not supported: {}", client.getEndpoint(), fwInfo); @@ -289,35 +363,70 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } 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()); + startUpdateUsingUrl(client, FW_URL_ID, fwInfo.getTargetUrl()); } else { log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), fwInfo.getTargetName(), fwInfo.getTargetVersion()); - startFirmwareUpdateUsingBinary(client, fwInfo); + startUpdateUsingBinary(client, fwInfo); } } } catch (Exception e) { - log.warn("[{}] failed to update client: {}", client.getEndpoint(), fwInfo, e); + log.info("[{}] failed to update client: {}", client.getEndpoint(), fwInfo, e); sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Internal server error: " + e.getMessage()); } } - private void startSoftwareUpdateIfNeeded(LwM2mClient client, LwM2MClientOtaInfo swInfo) { + private void startSoftwareUpdateIfNeeded(LwM2mClient client, LwM2MClientSwOtaInfo swInfo) { + try { + if (!swInfo.isSupported()) { + log.debug("[{}] Sw update is not supported: {}", client.getEndpoint(), swInfo); + sendStateUpdateToTelemetry(client, swInfo, OtaPackageUpdateStatus.FAILED, "Client does not support software update or profile misconfiguration!"); + } else if (swInfo.isUpdateRequired()) { + if (SoftwareUpdateState.INSTALLED.equals(swInfo.getUpdateState())) { + log.debug("[{}] Attempt to restore the update state: {}", client.getEndpoint(), swInfo.getUpdateState()); + executeSwUninstallForUpdate(client); + } else { + if (StringUtils.isNotEmpty(swInfo.getTargetUrl())) { + log.debug("[{}] Starting update to [{}{}] using URL: {}", client.getEndpoint(), swInfo.getTargetName(), swInfo.getTargetVersion(), swInfo.getTargetUrl()); + startUpdateUsingUrl(client, SW_PACKAGE_URI_ID, swInfo.getTargetUrl()); + } else { + log.debug("[{}] Starting update to [{}{}] using binary", client.getEndpoint(), swInfo.getTargetName(), swInfo.getTargetVersion()); + startUpdateUsingBinary(client, swInfo); + } + } + } + } catch (Exception e) { + log.info("[{}] failed to update client: {}", client.getEndpoint(), swInfo, e); + sendStateUpdateToTelemetry(client, swInfo, OtaPackageUpdateStatus.FAILED, "Internal server error: " + e.getMessage()); + } + } + + public void startUpdateUsingBinary(LwM2mClient client, LwM2MClientSwOtaInfo swInfo) { + this.transportService.process(client.getSession(), createOtaPackageRequestMsg(client.getSession(), swInfo.getType().name()), + new TransportServiceCallback<>() { + @Override + public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { + executor.submit(() -> doUpdateSoftwareUsingBinary(response, swInfo, client)); + } + @Override + public void onError(Throwable e) { + logService.log(client, "Failed to process software update: " + e.getMessage()); + } + }); } - private void startFirmwareUpdateUsingUrl(LwM2mClient client, String url) { - String targetIdVer = convertObjectIdToVersionedId(FW_URL_ID, client.getRegistration()); + private void startUpdateUsingUrl(LwM2mClient client, String id, String url) { + String targetIdVer = convertObjectIdToVersionedId(id, client.getRegistration()); 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) { - String versionedId = convertObjectIdToVersionedId(FW_PACKAGE_5_ID, client.getRegistration()); - this.transportService.process(client.getSession(), createOtaPackageRequestMsg(client.getSession(), OtaPackageType.FIRMWARE.name()), + public void startUpdateUsingBinary(LwM2mClient client, LwM2MClientFwOtaInfo fwInfo) { + this.transportService.process(client.getSession(), createOtaPackageRequestMsg(client.getSession(), fwInfo.getType().name()), new TransportServiceCallback<>() { @Override public void onSuccess(TransportProtos.GetOtaPackageResponseMsg response) { - executor.submit(() -> doUpdateFirmwareUsingBinary(response, fwInfo, versionedId, client)); + executor.submit(() -> doUpdateFirmwareUsingBinary(response, fwInfo, client)); } @Override @@ -327,34 +436,60 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl }); } - private void doUpdateFirmwareUsingBinary(TransportProtos.GetOtaPackageResponseMsg response, LwM2MClientOtaInfo fwInfo, String versionedId, LwM2mClient client) { + private void doUpdateFirmwareUsingBinary(TransportProtos.GetOtaPackageResponseMsg response, LwM2MClientFwOtaInfo info, 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() == FirmwareDeliveryMethod.BOTH.code) { - strategy = fwInfo.getFwStrategy(); + if (info.getDeliveryMethod() == null || info.getDeliveryMethod() == FirmwareDeliveryMethod.BOTH.code) { + strategy = info.getStrategy(); } else { - strategy = fwInfo.getDeliveryMethod() == FirmwareDeliveryMethod.PULL.code ? LwM2MFirmwareUpdateStrategy.OBJ_5_TEMP_URL : LwM2MFirmwareUpdateStrategy.OBJ_5_BINARY; + strategy = info.getDeliveryMethod() == FirmwareDeliveryMethod.PULL.code ? 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)); + startUpdateUsingBinary(client, convertObjectIdToVersionedId(FW_PACKAGE_5_ID, client.getRegistration()), otaPackageId); + break; + case OBJ_19_BINARY: + startUpdateUsingBinary(client, convertObjectIdToVersionedId(FW_PACKAGE_19_ID, client.getRegistration()), otaPackageId); break; case OBJ_5_TEMP_URL: - startFirmwareUpdateUsingUrl(client, fwInfo.getBaseUrl() + "/" + FIRMWARE_UPDATE_COAP_RESOURCE + "/" + otaPackageId.toString()); + startUpdateUsingUrl(client, FW_URL_ID, info.getBaseUrl() + "/" + FIRMWARE_UPDATE_COAP_RESOURCE + "/" + otaPackageId.toString()); + break; + default: + sendStateUpdateToTelemetry(client, info, OtaPackageUpdateStatus.FAILED, "Unsupported strategy: " + strategy.name()); + } + } else { + sendStateUpdateToTelemetry(client, info, OtaPackageUpdateStatus.FAILED, "Failed to fetch OTA package: " + response.getResponseStatus()); + } + } + + private void doUpdateSoftwareUsingBinary(TransportProtos.GetOtaPackageResponseMsg response, LwM2MClientSwOtaInfo info, LwM2mClient client) { + if (TransportProtos.ResponseStatus.SUCCESS.equals(response.getResponseStatus())) { + UUID otaPackageId = new UUID(response.getOtaPackageIdMSB(), response.getOtaPackageIdLSB()); + LwM2MSoftwareUpdateStrategy strategy = info.getStrategy(); + switch (strategy) { + case BINARY: + startUpdateUsingBinary(client, convertObjectIdToVersionedId(SW_PACKAGE_ID, client.getRegistration()), otaPackageId); + break; + case TEMP_URL: + startUpdateUsingUrl(client, SW_PACKAGE_URI_ID, info.getBaseUrl() + "/" + FIRMWARE_UPDATE_COAP_RESOURCE + "/" + otaPackageId.toString()); break; default: - sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Unsupported strategy: " + strategy.name()); + sendStateUpdateToTelemetry(client, info, OtaPackageUpdateStatus.FAILED, "Unsupported strategy: " + strategy.name()); } } else { - sendStateUpdateToTelemetry(client, fwInfo, OtaPackageUpdateStatus.FAILED, "Failed to fetch OTA package: " + response.getResponseStatus()); + sendStateUpdateToTelemetry(client, info, OtaPackageUpdateStatus.FAILED, "Failed to fetch OTA package: " + response.getResponseStatus()); } } + private void startUpdateUsingBinary(LwM2mClient client, String versionedId, UUID otaPackageId) { + 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)); + } + private TransportProtos.GetOtaPackageRequestMsg createOtaPackageRequestMsg(TransportProtos.SessionInfoProto sessionInfo, String nameFwSW) { return TransportProtos.GetOtaPackageRequestMsg.newBuilder() .setDeviceIdMSB(sessionInfo.getDeviceIdMSB()) @@ -370,6 +505,16 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, FW_EXECUTE_ID)); } + private void executeSwInstall(LwM2mClient client) { + TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(SW_INSTALL_ID).timeout(config.getTimeout()).build(); + downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, SW_INSTALL_ID)); + } + + private void executeSwUninstallForUpdate(LwM2mClient client) { + TbLwM2MExecuteRequest request = TbLwM2MExecuteRequest.builder().versionedId(SW_UN_INSTALL_ID).params("1").timeout(config.getTimeout()).build(); + downlinkHandler.sendExecuteRequest(client, request, new TbLwM2MExecuteCallback(logService, client, SW_INSTALL_ID)); + } + private Optional getAttributeValue(List attrs, String keyName) { for (TransportProtos.TsKvProto attr : attrs) { if (keyName.equals(attr.getKv().getKey())) { @@ -383,40 +528,41 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl return Optional.empty(); } - public LwM2MClientOtaInfo getOrInitFwInfo(LwM2mClient client) { + private LwM2MClientFwOtaInfo getOrInitFwInfo(LwM2mClient client) { return this.fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> { - LwM2MClientOtaInfo info = otaInfoStore.get(OtaPackageType.FIRMWARE, endpoint); + LwM2MClientFwOtaInfo info = otaInfoStore.getFw(endpoint); if (info == null) { var profile = clientContext.getProfile(client.getProfileId()); - info = new LwM2MClientOtaInfo(endpoint, OtaPackageType.FIRMWARE, - LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(profile.getClientLwM2mSettings().getFwUpdateStrategy()), - profile.getClientLwM2mSettings().getFwUpdateResource()); + info = new LwM2MClientFwOtaInfo(endpoint, profile.getClientLwM2mSettings().getFwUpdateResource(), + LwM2MFirmwareUpdateStrategy.fromStrategyFwByCode(profile.getClientLwM2mSettings().getFwUpdateStrategy())); update(info); } return info; }); } - private LwM2MClientOtaInfo getOrInitSwInfo(LwM2mClient client) { - return this.fwStates.computeIfAbsent(client.getEndpoint(), endpoint -> { - LwM2MClientOtaInfo info = otaInfoStore.get(OtaPackageType.SOFTWARE, endpoint); + private LwM2MClientSwOtaInfo getOrInitSwInfo(LwM2mClient client) { + return this.swStates.computeIfAbsent(client.getEndpoint(), endpoint -> { + LwM2MClientSwOtaInfo info = otaInfoStore.getSw(endpoint); if (info == null) { var profile = clientContext.getProfile(client.getProfileId()); - info = new LwM2MClientOtaInfo(endpoint, OtaPackageType.SOFTWARE, - LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(profile.getClientLwM2mSettings().getFwUpdateStrategy()), - profile.getClientLwM2mSettings().getSwUpdateResource()); + info = new LwM2MClientSwOtaInfo(endpoint, profile.getClientLwM2mSettings().getSwUpdateResource(), + LwM2MSoftwareUpdateStrategy.fromStrategySwByCode(profile.getClientLwM2mSettings().getFwUpdateStrategy())); update(info); } return info; }); + } + private void update(LwM2MClientFwOtaInfo info) { + otaInfoStore.putFw(info); } - private void update(LwM2MClientOtaInfo info) { - otaInfoStore.put(info); + private void update(LwM2MClientSwOtaInfo info) { + otaInfoStore.putSw(info); } - private void sendStateUpdateToTelemetry(LwM2mClient client, LwM2MClientOtaInfo fwInfo, OtaPackageUpdateStatus status, String log) { + 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()); @@ -462,22 +608,31 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl } } + private static Optional toOtaPackageUpdateStatus(SoftwareUpdateState swUpdateState) { + switch (swUpdateState) { + case INITIAL: + return Optional.empty(); + case DOWNLOAD_STARTED: + return Optional.of(DOWNLOADING); + case DOWNLOADED: + return Optional.of(DOWNLOADING); + case DELIVERED: + return Optional.of(DOWNLOADED); + case INSTALLED: + return Optional.empty(); + default: + throw new CodecException("Invalid value stateSw %d for SoftwareUpdateState.", swUpdateState); + } + } + /** * FirmwareUpdateStatus { * DOWNLOADING, DOWNLOADED, VERIFIED, UPDATING, UPDATED, FAILED */ - public static Optional toSwSateResultUpdateStatus(SoftwareUpdateState softwareUpdateState, SoftwareUpdateResult softwareUpdateResult) { + public static Optional toOtaPackageUpdateStatus(SoftwareUpdateResult softwareUpdateResult) { switch (softwareUpdateResult) { case INITIAL: - switch (softwareUpdateState) { - case INITIAL: - case DOWNLOAD_STARTED: - return Optional.of(DOWNLOADING); - case DOWNLOADED: - return Optional.of(DOWNLOADED); - case DELIVERED: - return Optional.of(VERIFIED); - } + return Optional.empty(); case DOWNLOADING: return Optional.of(DOWNLOADING); case SUCCESSFULLY_INSTALLED: @@ -495,7 +650,7 @@ 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.", softwareUpdateState.name(), softwareUpdateResult.name()); + throw new CodecException("Invalid value stateFw %s for FirmwareUpdateStatus.", 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 260c266906..48936862f3 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 @@ -20,60 +20,42 @@ import lombok.Data; import lombok.NoArgsConstructor; 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.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; @Data @NoArgsConstructor -public class LwM2MClientOtaInfo { +public abstract class LwM2MClientOtaInfo { private String endpoint; - private OtaPackageType type; - private String baseUrl; - private boolean targetFetchFailure; - private String targetName; - private String targetVersion; - private String targetUrl; - - private boolean currentFetchFailure; - private String currentName; - private String currentVersion3; - private String currentVersion5; - private Integer deliveryMethod; + protected String targetName; + protected String targetVersion; + protected String targetUrl; //TODO: use value from device if applicable; - private LwM2MFirmwareUpdateStrategy fwStrategy; - private LwM2MSoftwareUpdateStrategy swStrategy; - private FirmwareUpdateState updateState; - private FirmwareUpdateResult updateResult; + protected Strategy strategy; + protected State updateState; + protected Result result; - private String failedPackageId; - private int retryAttempts; + protected String failedPackageId; + protected int retryAttempts; - public LwM2MClientOtaInfo(String endpoint, OtaPackageType type, LwM2MFirmwareUpdateStrategy fwStrategy, String baseUrl) { - this.endpoint = endpoint; - this.type = type; - this.fwStrategy = fwStrategy; - this.baseUrl = baseUrl; - } + protected String currentName; + protected String currentVersion3; + protected String currentVersion; - public LwM2MClientOtaInfo(String endpoint, OtaPackageType type, LwM2MSoftwareUpdateStrategy swStrategy, String baseUrl) { + public LwM2MClientOtaInfo(String endpoint, String baseUrl, Strategy strategy) { this.endpoint = endpoint; - this.type = type; - this.swStrategy = swStrategy; this.baseUrl = baseUrl; + this.strategy = strategy; } - public void updateTarget(String targetName, String targetVersion, Optional newFirmwareUrl) { + public void updateTarget(String targetName, String targetVersion, Optional newTargetUrl) { this.targetName = targetName; this.targetVersion = targetVersion; - this.targetUrl = newFirmwareUrl.orElse(null); + this.targetUrl = newTargetUrl.orElse(null); } @JsonIgnore @@ -82,7 +64,7 @@ public class LwM2MClientOtaInfo { return false; } else { String targetPackageId = getPackageId(targetName, targetVersion); - String currentPackageIdUsingObject5 = getPackageId(currentName, currentVersion5); + String currentPackageIdUsingObject5 = getPackageId(currentName, currentVersion); if (StringUtils.isNotEmpty(failedPackageId) && failedPackageId.equals(targetPackageId)) { return false; } else { @@ -99,25 +81,15 @@ public class LwM2MClientOtaInfo { @JsonIgnore public boolean isSupported() { - return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion5) || StringUtils.isNotEmpty(currentVersion3); + return StringUtils.isNotEmpty(currentName) || StringUtils.isNotEmpty(currentVersion) || StringUtils.isNotEmpty(currentVersion3); } - public void update(FirmwareUpdateResult updateResult) { - this.updateResult = updateResult; - switch (updateResult) { - case INITIAL: - break; - case UPDATE_SUCCESSFULLY: - retryAttempts = 0; - break; - default: - failedPackageId = getPackageId(targetName, targetVersion); - break; - } - } + public abstract void update(Result result); - private static String getPackageId(String name, String version) { + protected static String getPackageId(String name, String version) { return (StringUtils.isNotEmpty(name) ? name : "") + (StringUtils.isNotEmpty(version) ? version : ""); } + public abstract OtaPackageType getType(); + } 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 2aa7f39872..df9505f887 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 @@ -26,9 +26,9 @@ public interface LwM2MOtaUpdateService { void forceFirmwareUpdate(LwM2mClient client); - void onTargetFirmwareUpdate(LwM2mClient client, String newFirmwareTitle, String newFirmwareVersion, Optional newFirmwareUrl); + void onTargetFirmwareUpdate(LwM2mClient client, String newFwTitle, String newFwVersion, Optional newFwUrl); - void onTargetSoftwareUpdate(LwM2mClient client, String newSoftwareTitle, String newSoftwareVersion); + void onTargetSoftwareUpdate(LwM2mClient client, String newSwTitle, String newSwVersion, Optional newSwUrl); void onCurrentFirmwareNameUpdate(LwM2mClient client, String name); @@ -38,11 +38,21 @@ public interface LwM2MOtaUpdateService { void onCurrentFirmwareVersion3Update(LwM2mClient client, String version); - void onCurrentFirmwareVersion5Update(LwM2mClient client, String version); + void onCurrentFirmwareVersionUpdate(LwM2mClient client, String version); void onCurrentFirmwareStateUpdate(LwM2mClient client, Long state); void onCurrentFirmwareResultUpdate(LwM2mClient client, Long result); void onCurrentFirmwareDeliveryMethodUpdate(LwM2mClient lwM2MClient, Long value); + + void onCurrentSoftwareNameUpdate(LwM2mClient lwM2MClient, String name); + + void onCurrentSoftwareVersion3Update(LwM2mClient lwM2MClient, String version); + + void onCurrentSoftwareVersionUpdate(LwM2mClient client, String version); + + void onCurrentSoftwareStateUpdate(LwM2mClient lwM2MClient, Long value); + + void onCurrentSoftwareResultUpdate(LwM2mClient client, Long result); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MClientFwOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MClientFwOtaInfo.java new file mode 100644 index 0000000000..49faa2760d --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/firmware/LwM2MClientFwOtaInfo.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.ota.firmware; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; + +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class LwM2MClientFwOtaInfo extends LwM2MClientOtaInfo { + + private Integer deliveryMethod; + + public LwM2MClientFwOtaInfo(String endpoint, String baseUrl, LwM2MFirmwareUpdateStrategy strategy) { + super(endpoint, baseUrl, strategy); + } + + @JsonIgnore + @Override + public OtaPackageType getType() { + return OtaPackageType.FIRMWARE; + } + + public void update(FirmwareUpdateResult result) { + this.result = result; + switch (result) { + case INITIAL: + break; + case UPDATE_SUCCESSFULLY: + retryAttempts = 0; + break; + default: + failedPackageId = getPackageId(targetName, targetVersion); + break; + } + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java new file mode 100644 index 0000000000..76795d922f --- /dev/null +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/LwM2MClientSwOtaInfo.java @@ -0,0 +1,57 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; +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.firmware.LwM2MFirmwareUpdateStrategy; + +@Data +@EqualsAndHashCode(callSuper = true) +@NoArgsConstructor +public class LwM2MClientSwOtaInfo extends LwM2MClientOtaInfo { + + public LwM2MClientSwOtaInfo(String endpoint, String baseUrl, LwM2MSoftwareUpdateStrategy strategy) { + super(endpoint, baseUrl, strategy); + } + + @JsonIgnore + @Override + public OtaPackageType getType() { + return OtaPackageType.SOFTWARE; + } + + + public void update(SoftwareUpdateResult result) { + this.result = result; + switch (result) { + case INITIAL: + break; + //TODO: implement + default: + failedPackageId = getPackageId(targetName, targetVersion); + break; + } + } + +} diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java index e99d4e4aca..7bf5473a1b 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/ota/software/SoftwareUpdateResult.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.transport.lwm2m.server.ota.software; +import lombok.Getter; + /** * SW Update Result * Contains the result of downloading or installing/uninstalling the software @@ -56,6 +58,7 @@ public enum SoftwareUpdateResult { public int code; public String type; + @Getter public boolean isAgain; SoftwareUpdateResult(int code, String type, boolean isAgain) { 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 f300e00675..d870d00e8c 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 @@ -82,7 +82,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { 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()); + log.debug("Received params: {}", rpcRequst.getParams()); // 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)) { @@ -308,20 +308,19 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } private void cleanupOldSessions() { - log.warn("4.1) before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); + log.debug("Before rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); if (rpcSubscriptions.size() > 0) { long currentTime = System.currentTimeMillis(); Set rpcSubscriptionsToRemove = rpcSubscriptions.entrySet().stream().filter(kv -> currentTime > kv.getValue()).map(Map.Entry::getKey).collect(Collectors.toSet()); - log.warn("4.2) System.currentTimeMillis(): [{}]", System.currentTimeMillis()); - log.warn("4.3) rpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove); + log.debug("RpcSubscriptionsToRemove: [{}]", rpcSubscriptionsToRemove); rpcSubscriptionsToRemove.forEach(rpcSubscriptions::remove); } - log.warn("4.4) after rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); + log.debug("After rpcSubscriptions.size(): [{}]", rpcSubscriptions.size()); } @Override public void onToDeviceRpcResponse(TransportProtos.ToDeviceRpcResponseMsg toDeviceResponse, TransportProtos.SessionInfoProto sessionInfo) { - log.warn("5) onToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); + log.debug("OnToDeviceRpcResponse: [{}], sessionUUID: [{}]", toDeviceResponse, new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB())); transportService.process(sessionInfo, toDeviceResponse, null); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MClientOtaInfoStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MClientOtaInfoStore.java index ee46adcdf8..14b5ce476a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MClientOtaInfoStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbDummyLwM2MClientOtaInfoStore.java @@ -15,18 +15,28 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; public class TbDummyLwM2MClientOtaInfoStore implements TbLwM2MClientOtaInfoStore { @Override - public LwM2MClientOtaInfo get(OtaPackageType type, String endpoint) { + public LwM2MClientFwOtaInfo getFw(String endpoint) { return null; } @Override - public void put(LwM2MClientOtaInfo info) { + public LwM2MClientSwOtaInfo getSw(String endpoint) { + return null; + } + + @Override + public void putFw(LwM2MClientFwOtaInfo info) { + + } + + @Override + public void putSw(LwM2MClientSwOtaInfo info) { } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientOtaInfoStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientOtaInfoStore.java index b80321ccfb..fb7bff4337 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientOtaInfoStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientOtaInfoStore.java @@ -17,10 +17,16 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; public interface TbLwM2MClientOtaInfoStore { - LwM2MClientOtaInfo get(OtaPackageType type, String endpoint); + LwM2MClientFwOtaInfo getFw(String endpoint); - void put(LwM2MClientOtaInfo info); + LwM2MClientSwOtaInfo getSw(String endpoint); + + void putFw(LwM2MClientFwOtaInfo info); + + void putSw(LwM2MClientSwOtaInfo info); } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java index 36283ef3ff..fcc588473a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mRedisClientOtaInfoStore.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server.store; import org.eclipse.leshan.server.security.NonUniqueSecurityInfoException; import org.eclipse.leshan.server.security.SecurityInfo; +import org.jetbrains.annotations.Nullable; import org.nustaq.serialization.FSTConfiguration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.integration.redis.util.RedisLockRegistry; @@ -24,6 +25,8 @@ import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; +import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; +import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; import java.util.concurrent.locks.Lock; @@ -36,19 +39,36 @@ public class TbLwM2mRedisClientOtaInfoStore implements TbLwM2MClientOtaInfoStore this.connectionFactory = connectionFactory; } - @Override - public LwM2MClientOtaInfo get(OtaPackageType type, String endpoint) { + private void put(OtaPackageType type, LwM2MClientOtaInfo info) { try (var connection = connectionFactory.getConnection()) { - byte[] data = connection.get((OTA_EP + type + endpoint).getBytes()); - return JacksonUtil.fromBytes(data, LwM2MClientOtaInfo.class); + connection.set((OTA_EP + type + info.getEndpoint()).getBytes(), JacksonUtil.toString(info).getBytes()); } } @Override - public void put(LwM2MClientOtaInfo info) { + public LwM2MClientFwOtaInfo getFw(String endpoint) { + return getLwM2MClientOtaInfo(OtaPackageType.FIRMWARE, endpoint, LwM2MClientFwOtaInfo.class); + } + + @Override + public void putFw(LwM2MClientFwOtaInfo info) { + put(OtaPackageType.FIRMWARE, info); + } + + @Override + public LwM2MClientSwOtaInfo getSw(String endpoint) { + return getLwM2MClientOtaInfo(OtaPackageType.SOFTWARE, endpoint, LwM2MClientSwOtaInfo.class); + } + + @Override + public void putSw(LwM2MClientSwOtaInfo info) { + put(OtaPackageType.SOFTWARE, info); + } + + private > T getLwM2MClientOtaInfo(OtaPackageType type, String endpoint, Class clazz) { try (var connection = connectionFactory.getConnection()) { - connection.set((OTA_EP + info.getType() + info.getEndpoint()).getBytes(), JacksonUtil.toString(info).getBytes()); + byte[] data = connection.get((OTA_EP + type + endpoint).getBytes()); + return JacksonUtil.fromBytes(data, clazz); } } - } 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 1850212b6e..8f8e4f307e 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 @@ -90,7 +90,6 @@ import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -111,11 +110,16 @@ import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.c 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_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; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_3_VER_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_NAME_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_STATE_ID; +import static org.thingsboard.server.transport.lwm2m.server.ota.DefaultLwM2MOtaUpdateService.SW_VER_ID; @Slf4j @@ -238,7 +242,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl executor.submit(() -> { LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); try { - log.warn("[{}] [{{}] Client: update after Registration", registration.getEndpoint(), registration.getId()); + log.info("[{}] [{{}] 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); this.reportActivityAndRegister(lwM2MClient.getSession()); @@ -318,7 +322,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl } public void onUpdateValueAfterReadCompositeResponse(Registration registration, ReadCompositeResponse response) { - log.warn("201) ReadCompositeResponse: [{}]", response); + log.trace("ReadCompositeResponse: [{}]", response); if (response.getContent() != null) { LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); response.getContent().forEach((k, v) -> { @@ -407,19 +411,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl Lwm2mDeviceProfileTransportConfiguration profile = clientContext.getProfile(lwM2MClient.getProfileId()); Set supportedObjects = clientContext.getSupportedIdVerInClient(lwM2MClient); if (supportedObjects != null && supportedObjects.size() > 0) { - // #1 this.sendReadRequests(lwM2MClient, profile, supportedObjects); - // test composite - String[] paths = new String[]{"/3/0", "/1/0", "/5/0"}; -// String [] paths = new String[] {"/5"}; -// String [] paths = new String[] {"/"}; -// String [] paths = new String[] {"/9"}; -// defaultLwM2MDownlinkMsgHandler.sendReadCompositeRequest(lwM2MClient, paths, this); - Map nodes = new HashMap<>(); - nodes.put("/3/0/14", "+02"); - nodes.put("/1/0/2", 100); - nodes.put("/5/0/1", "coap://localhost:5685"); -// defaultLwM2MDownlinkMsgHandler.sendWriteCompositeRequest(lwM2MClient, nodes, this); this.sendObserveRequests(lwM2MClient, profile, supportedObjects); this.sendWriteAttributeRequests(lwM2MClient, profile, supportedObjects); // Removed. Used only for debug. @@ -531,14 +523,24 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl otaService.onCurrentFirmwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); } else if (path.equals(convertObjectIdToVersionedId(FW_3_VER_ID, registration))) { otaService.onCurrentFirmwareVersion3Update(lwM2MClient, (String) lwM2mResource.getValue()); - } else if (path.equals(convertObjectIdToVersionedId(FW_5_VER_ID, registration))) { - otaService.onCurrentFirmwareVersion5Update(lwM2MClient, (String) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(FW_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()); } else if (path.equals(convertObjectIdToVersionedId(FW_DELIVERY_METHOD, registration))) { otaService.onCurrentFirmwareDeliveryMethodUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(SW_NAME_ID, registration))) { + otaService.onCurrentSoftwareNameUpdate(lwM2MClient, (String) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(SW_VER_ID, registration))) { + otaService.onCurrentSoftwareVersionUpdate(lwM2MClient, (String) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(SW_3_VER_ID, registration))) { + otaService.onCurrentSoftwareVersion3Update(lwM2MClient, (String) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(SW_STATE_ID, registration))) { + otaService.onCurrentSoftwareStateUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); + } else if (path.equals(convertObjectIdToVersionedId(SW_RESULT_ID, registration))) { + otaService.onCurrentSoftwareResultUpdate(lwM2MClient, (Long) lwM2mResource.getValue()); } this.updateAttrTelemetry(registration, Collections.singleton(path)); } else { @@ -698,7 +700,7 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl @Override public void onWriteCompositeResponseOk(LwM2mClient client, WriteCompositeRequest request) { - log.warn("202) ReadCompositeResponse: [{}]", request.getNodes()); + log.trace("ReadCompositeResponse: [{}]", request.getNodes()); request.getNodes().forEach((k, v) -> { this.updateResourcesValue(client, (LwM2mResource) v, k.toString()); }); From a0c504ade6ff4871fca8155f9374e718bfb1ef7e Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 30 Jun 2021 12:06:30 +0300 Subject: [PATCH 182/231] UI: LwM2M device profile add new setting compositeOperationsSupport --- ...2m-device-profile-transport-configuration.component.html | 2 ++ ...2m-device-profile-transport-configuration.component.scss | 4 ++++ ...wm2m-device-profile-transport-configuration.component.ts | 6 ++++-- .../profile/device/lwm2m/lwm2m-profile-config.models.ts | 4 +++- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 ++- 5 files changed, 15 insertions(+), 4 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 a54a33733d..571820acb2 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 @@ -168,6 +168,8 @@ + {{ 'device-profile.lwm2m.composite-operations-support' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.scss index 25532ec165..c096e5462a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-device-profile-transport-configuration.component.scss @@ -24,4 +24,8 @@ color: rgba(0, 0, 0, .7); } } + + .mat-slider { + margin: 8px 0 8px 10px; + } } 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 90f68bb92c..777431ea3a 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 @@ -117,7 +117,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro swUpdateStrategy: [1, []], fwUpdateResource: [{value: '', disabled: true}, []], swUpdateResource: [{value: '', disabled: true}, []], - powerMode: [null, Validators.required] + powerMode: [PowerMode.DRX, Validators.required], + compositeOperationsSupport: [false] }) }); this.lwm2mDeviceConfigFormGroup = this.fb.group({ @@ -258,7 +259,8 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro swUpdateStrategy: this.configurationValue.clientLwM2mSettings.swUpdateStrategy || 1, fwUpdateResource: fwResource, swUpdateResource: swResource, - powerMode: this.configurationValue.clientLwM2mSettings.powerMode + powerMode: this.configurationValue.clientLwM2mSettings.powerMode || PowerMode.DRX, + compositeOperationsSupport: this.configurationValue.clientLwM2mSettings.compositeOperationsSupport || false } }, {emitEvent: false}); 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 bc48182e9d..b550e4f5ae 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 @@ -175,6 +175,7 @@ export interface ClientLwM2mSettings { fwUpdateResource: string; swUpdateResource: string; powerMode: PowerMode; + compositeOperationsSupport: boolean; } export interface ObservableAttributes { @@ -231,7 +232,8 @@ export function getDefaultProfileClientLwM2mSettingsConfig(): ClientLwM2mSetting swUpdateStrategy: 1, fwUpdateResource: DEFAULT_FW_UPDATE_RESOURCE, swUpdateResource: DEFAULT_SW_UPDATE_RESOURCE, - powerMode: PowerMode.DRX + powerMode: PowerMode.DRX, + compositeOperationsSupport: 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 89c31a70c2..bf6b576952 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1328,7 +1328,8 @@ "step": "Step", "min-evaluation-period": "Minimum evaluation period", "max-evaluation-period": "Maximum evaluation period" - } + }, + "composite-operations-support": "Supports composite Read/Write/Observe operations" }, "snmp": { "add-communication-config": "Add communication config", From d6bbeae3359973822e054357eb91313a5b4230ab Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 30 Jun 2021 16:01:20 +0300 Subject: [PATCH 183/231] fix for MqttSslHandlerProvider component injection & fix for skip_check_client_certificate parameter injection --- .../server/transport/mqtt/MqttSslHandlerProvider.java | 1 - .../thingsboard/server/transport/mqtt/MqttTransportContext.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 64efb5f036..2a0419b43d 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -55,7 +55,6 @@ import java.util.concurrent.TimeUnit; */ @Slf4j @Component("MqttSslHandlerProvider") -@ConditionalOnExpression("'${transport.mqtt.enabled}'=='true'") @ConditionalOnProperty(prefix = "transport.mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false) public class MqttSslHandlerProvider { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java index 30f263cba4..f160936c7c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportContext.java @@ -52,7 +52,7 @@ public class MqttTransportContext extends TransportContext { private Integer maxPayloadSize; @Getter - @Value("${transport.mqtt.netty.skip_validity_check_for_client_cert:false}") + @Value("${transport.mqtt.ssl.skip_validity_check_for_client_cert:false}") private boolean skipValidityCheckForClientCert; @Getter From 3d8f19c23e887a9563638b5549657ffa77269bfd Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 30 Jun 2021 16:08:59 +0300 Subject: [PATCH 184/231] Lwm2m: opaque --- .../transport/lwm2m/config/LwM2mVersion.java | 9 ++- .../lwm2m/server/client/LwM2mClient.java | 79 ++++++++++++++----- .../server/client/LwM2mClientContextImpl.java | 1 - .../lwm2m/server/client/ResourceValue.java | 12 ++- .../rpc/DefaultLwM2MRpcRequestHandler.java | 38 ++++++--- .../server/rpc/RpcReadResponseCallback.java | 13 +-- .../RpcReadResponseCompositeCallback.java | 3 +- .../uplink/DefaultLwM2MUplinkMsgHandler.java | 12 --- 8 files changed, 104 insertions(+), 63 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2mVersion.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2mVersion.java index 079dc296d9..18288246e4 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2mVersion.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/config/LwM2mVersion.java @@ -20,8 +20,8 @@ import org.eclipse.leshan.core.LwM2m.Version; import org.eclipse.leshan.core.request.ContentFormat; public enum LwM2mVersion { - VERSION_1_0(0, Version.V1_0, ContentFormat.TLV), - VERSION_1_1(1, Version.V1_1, ContentFormat.TEXT); + VERSION_1_0(0, Version.V1_0, ContentFormat.TLV, false), + VERSION_1_1(1, Version.V1_1, ContentFormat.TEXT, true); @Getter private final int code; @@ -29,11 +29,14 @@ public enum LwM2mVersion { private final Version version; @Getter private final ContentFormat contentFormat; + @Getter + private final boolean composite; - LwM2mVersion(int code, Version version, ContentFormat contentFormat) { + LwM2mVersion(int code, Version version, ContentFormat contentFormat, boolean composite) { this.code = code; this.version = version; this.contentFormat = contentFormat; + this.composite = composite; } public static LwM2mVersion fromVersion(Version version) { 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 8cf6f20f36..588c8e77f4 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,13 +20,13 @@ 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.LwM2mObject; -import org.eclipse.leshan.core.node.LwM2mObjectInstance; +import org.eclipse.leshan.core.node.LwM2mMultipleResource; 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.core.util.Hex; import org.eclipse.leshan.server.model.LwM2mModelProvider; import org.eclipse.leshan.server.registration.Registration; import org.thingsboard.server.common.data.Device; @@ -43,6 +43,7 @@ import java.io.IOException; import java.io.ObjectInputStream; import java.io.Serializable; import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -52,6 +53,7 @@ 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.thingsboard.server.common.data.lwm2m.LwM2mConstants.LWM2M_SEPARATOR_PATH; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.LWM2M_OBJECT_VERSION_DEFAULT; import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.convertObjectIdToVersionedId; @@ -239,36 +241,71 @@ public class LwM2mClient implements Serializable { .getObjectModel(pathIds.getObjectId()) : null; } - public String objectToString(LwM2mObject lwM2mObject) { + public Optional contentToString(Object content) { + String value = null; + LwM2mResource resource = null; + String key = null; + if (content instanceof HashMap && ((HashMap) content).size() == 1) { + key = ((HashMap) content).keySet().toArray()[0].toString(); + if (((HashMap) content).values().toArray()[0] != null) { + if (((HashMap) content).values().toArray()[0] instanceof LwM2mResource) { + resource = (LwM2mResource) ((HashMap) content).values().toArray()[0]; + } + } + } else if (content instanceof LwM2mResource) { + resource = (LwM2mResource) content; + } + if (resource != null && resource.getType() == OPAQUE) { + value = this.resourceToString(resource, key); + } + value = value == null ? content.toString() : value; + return Optional.of(String.format("%s", value)); + } + + public String resourceToString(LwM2mResource resource, String key) { + String value = null; StringBuilder builder = new StringBuilder(); - builder.append("LwM2mObject [id=").append(lwM2mObject.getId()).append(", instances={"); - lwM2mObject.getInstances().forEach((instId, inst) -> { - builder.append(instId).append("=").append(this.instanceToString(inst)).append(", "); - }); - int startInd = builder.lastIndexOf(", "); - if (startInd > 0) { - builder.delete(startInd, startInd + 2); + if (resource instanceof LwM2mSingleResource && ((byte[]) resource.getValue()).length > 0) { + builder.append("LwM2mSingleResource"); + if (key == null) { + builder.append(" id=").append(String.valueOf(resource.getId())); + } else { + builder.append(" key=").append(key); + } + builder.append(" value=").append(opaqueToString((byte[]) resource.getValue())); + builder.append(" type=").append(OPAQUE.toString()); + value = builder.toString(); + } else if (resource instanceof LwM2mMultipleResource) { + builder.append("LwM2mMultipleResource"); + if (key == null) { + builder.append(" id=").append(String.valueOf(resource.getId())); + } else { + builder.append(" key=").append(key); + } + builder.append(" values={"); + builder.append(multiInstanceOpaqueToString((LwM2mMultipleResource) resource)); + builder.append("}"); + builder.append(" type=").append(OPAQUE.toString()); + value = builder.toString(); } - builder.append("}]"); - return builder.toString(); + return value; } - public String instanceToString(LwM2mObjectInstance objectInstance) { + private String multiInstanceOpaqueToString(LwM2mMultipleResource resource) { StringBuilder builder = new StringBuilder(); - builder.append("LwM2mObjectInstance [id=").append(objectInstance.getId()).append(", resources={"); - objectInstance.getResources().forEach((resId, res) -> { - builder.append(resId).append("=").append(this.resourceToString(res)).append(", "); + resource.getInstances().values().stream().map(v -> { + return builder.append(" id=").append(v.getId()).append(" value=").append(Hex.encodeHexString((byte[]) v.getValue())).append(", "); }); int startInd = builder.lastIndexOf(", "); if (startInd > 0) { builder.delete(startInd, startInd + 2); } - builder.append("}]"); return builder.toString(); } - public String resourceToString(LwM2mResource lwM2mResource) { - return lwM2mResource.isMultiInstances() ? lwM2mResource.getInstances().toString() : lwM2mResource.getValue().toString(); + private String opaqueToString(byte[] value) { + String opaque = Hex.encodeHexString(value); + return opaque.length() > 1024 ? opaque.substring(0, 1024) : opaque; } public Collection getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, @@ -366,5 +403,9 @@ public class LwM2mClient implements Serializable { this.lock = new ReentrantLock(); } + public boolean isComposite (LwM2mClientContext clientContext) { + return LwM2mVersion.fromVersionStr(registration.getLwM2mVersion()).isComposite() & + clientContext.getProfile(this.profileId).getClientLwM2mSettings().isComposite(); + } } 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 261220f013..178602a16b 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.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.TransportDeviceProfileCache; -import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java index 38a1815b06..7f61caf58a 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/client/ResourceValue.java @@ -21,6 +21,7 @@ import org.eclipse.leshan.core.node.LwM2mResource; import org.eclipse.leshan.core.node.LwM2mResourceInstance; import java.io.Serializable; +import java.util.stream.Collectors; @Data public class ResourceValue implements Serializable { @@ -45,9 +46,14 @@ public class ResourceValue implements Serializable { private static TbLwM2MResource toTbLwM2MResource(LwM2mResource lwM2mResource) { if (lwM2mResource.isMultiInstances()) { - TbLwM2MResourceInstance[] instances = (TbLwM2MResourceInstance[]) lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).toArray(); - return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances); - } else { + if ( lwM2mResource.getInstances().values().size() > 0) { + TbLwM2MResourceInstance [] instances = lwM2mResource.getInstances().values().stream().map(ResourceValue::toTbLwM2MResourceInstance).collect(Collectors.toSet()).toArray(new TbLwM2MResourceInstance[0]); + return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), instances); + } + else { + return new TbLwM2MMultipleResource(lwM2mResource.getId(), lwM2mResource.getType(), new TbLwM2MResourceInstance[0]); + } + } else { return new TbLwM2MSingleResource(lwM2mResource.getId(), lwM2mResource.getValue(), lwM2mResource.getType()); } } 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 f300e00675..dd263b33e0 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 @@ -97,7 +97,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { return; } try { - if (operationType.isHasObjectId()) { + if (operationType.isHasObjectId()) { String objectId = getIdFromParameters(client, rpcRequst); switch (operationType) { case READ: @@ -131,16 +131,23 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } } else if (operationType.isComposite()) { - switch (operationType) { - case READ_COMPOSITE: - sendReadCompositeRequest(client, rpcRequst); - break; - case WRITE_COMPOSITE: - sendWriteCompositeRequest(client, rpcRequst); - break; - default: - throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); - } + if (client.isComposite(clientContext)) { + switch (operationType) { + case READ_COMPOSITE: + sendReadCompositeRequest(client, rpcRequst); + break; + case WRITE_COMPOSITE: + sendWriteCompositeRequest(client, rpcRequst); + break; + default: + throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); + } + } + else { + this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), + ResponseCode.INTERNAL_SERVER_ERROR.getName(), "This device does not support Composite Operation"); + return; + } } else { switch (operationType) { case OBSERVE_CANCEL_ALL: @@ -239,6 +246,15 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { downlinkHandler.sendWriteReplaceRequest(client, request, rpcCallback); } + /** + * Map nodes = new HashMap<>(); + * nodes.put("/3/0/14", "+02"); + * nodes.put("/1/0/2", 100); + * nodes.put("/5/0/1", "coap://localhost:5685"); + * // defaultLwM2MDownlinkMsgHandler.sendWriteCompositeRequest(lwM2MClient, nodes, this); + * @param client + * @param requestMsg + */ private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { RpcWriteCompositeRequest nodes = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class); // TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) 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 6f7eb4b2b7..3a53ef5d5d 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 @@ -15,9 +15,6 @@ */ 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; @@ -35,14 +32,6 @@ public class RpcReadResponseCallback, T extends ReadRe @Override protected Optional serializeSuccessfulResponse(T response) { - Object value = null; - if (response.getContent() instanceof LwM2mObject) { - value = client.objectToString((LwM2mObject) response.getContent()); - } else if (response.getContent() instanceof LwM2mObjectInstance) { - value = client.instanceToString((LwM2mObjectInstance) response.getContent()); - } else if (response.getContent() instanceof LwM2mResource) { - value = client.resourceToString((LwM2mResource) response.getContent()); - } - return Optional.of(String.format("%s", value)); + return client.contentToString (response.getContent()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java index 8ce3a0a672..e73fb54ca7 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java @@ -16,7 +16,6 @@ package org.thingsboard.server.transport.lwm2m.server.rpc.composite; import org.eclipse.leshan.core.request.LwM2mRequest; -import org.eclipse.leshan.core.request.ReadCompositeRequest; import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.gen.transport.TransportProtos; @@ -34,6 +33,6 @@ public class RpcReadResponseCompositeCallback, T exten @Override protected Optional serializeSuccessfulResponse(T response) { - return Optional.of(String.format("%s", response.getContent().toString())); + return client.contentToString (response.getContent()); } } 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 cd8b19c60a..ed2a1802ff 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 @@ -91,7 +91,6 @@ import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -427,17 +426,6 @@ public class DefaultLwM2MUplinkMsgHandler extends LwM2MExecutorAwareService impl if (supportedObjects != null && supportedObjects.size() > 0) { // #1 this.sendReadRequests(lwM2MClient, profile, supportedObjects); - // test composite - String[] paths = new String[]{"/3/0", "/1/0", "/5/0"}; -// String [] paths = new String[] {"/5"}; -// String [] paths = new String[] {"/"}; -// String [] paths = new String[] {"/9"}; -// defaultLwM2MDownlinkMsgHandler.sendReadCompositeRequest(lwM2MClient, paths, this); - Map nodes = new HashMap<>(); - nodes.put("/3/0/14", "+02"); - nodes.put("/1/0/2", 100); - nodes.put("/5/0/1", "coap://localhost:5685"); -// defaultLwM2MDownlinkMsgHandler.sendWriteCompositeRequest(lwM2MClient, nodes, this); this.sendObserveRequests(lwM2MClient, profile, supportedObjects); this.sendWriteAttributeRequests(lwM2MClient, profile, supportedObjects); // Removed. Used only for debug. From 6083036f946c1bcaffd6b51dda9d83b4cfd531f5 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 30 Jun 2021 16:45:03 +0300 Subject: [PATCH 185/231] Lwm2m: opaque merge from master --- .../server/transport/lwm2m/server/client/LwM2mClient.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1028e1927d..a9af68160c 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 @@ -407,7 +407,7 @@ public class LwM2mClient implements Serializable { public boolean isComposite (LwM2mClientContext clientContext) { return LwM2mVersion.fromVersionStr(registration.getLwM2mVersion()).isComposite() & - clientContext.getProfile(this.profileId).getClientLwM2mSettings().isComposite(); + clientContext.getProfile(this.profileId).getClientLwM2mSettings().isCompositeOperationsSupport(); } } From 9f499d9188ea437910d1c0b33c1977f9eb54b559 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 30 Jun 2021 18:04:06 +0300 Subject: [PATCH 186/231] ResourceUtil implementation --- .../service/edge/rpc/EdgeGrpcService.java | 8 +- .../server/coapserver/TbCoapDtlsSettings.java | 3 +- .../server/common/data/ResourceUtils.java | 86 +++++++++++++++++++ .../thingsboard/edge/rpc/EdgeGrpcClient.java | 5 +- .../config/LwM2MTransportServerConfig.java | 26 ++---- .../mqtt/MqttSslHandlerProvider.java | 10 +-- .../client/tools/MqttSslClient.java | 10 +-- 7 files changed, 110 insertions(+), 38 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ResourceUtils.java diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index ab0857f863..8450a24aea 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -28,6 +28,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.EdgeId; @@ -48,6 +49,7 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.Collections; import java.util.Map; import java.util.UUID; @@ -103,9 +105,9 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i .addService(this); if (sslEnabled) { try { - File certFile = new File(Resources.getResource(certFileResource).toURI()); - File privateKeyFile = new File(Resources.getResource(privateKeyResource).toURI()); - builder.useTransportSecurity(certFile, privateKeyFile); + InputStream certFileIs = ResourceUtils.getInputStream(this, certFileResource); + InputStream privateKeyFileIs = ResourceUtils.getInputStream(this, privateKeyResource); + builder.useTransportSecurity(certFileIs, privateKeyFileIs); } catch (Exception e) { log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e); throw new RuntimeException("Unable to set up SSL context!", e); 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 d5605ee29b..c25f57d41d 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 @@ -26,6 +26,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.Component; +import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; @@ -87,7 +88,7 @@ public class TbCoapDtlsSettings { } else { DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(); configBuilder.setAddress(getInetSocketAddress()); - String keyStoreFilePath = Resources.getResource(keyStoreFile).getPath(); + String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile); SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath); SecurityMode securityMode = securityModeOpt.get(); if (securityMode.equals(SecurityMode.NO_AUTH)) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ResourceUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceUtils.java new file mode 100644 index 0000000000..754476bc26 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ResourceUtils.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.common.data; + +import com.google.common.io.Resources; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.net.URI; +import java.net.URL; + +@Slf4j +public class ResourceUtils { + + public static InputStream getInputStream(Object classLoaderSource, String filePath) { + return getInputStream(classLoaderSource.getClass().getClassLoader(), filePath); + } + + public static InputStream getInputStream(ClassLoader classLoader, String filePath) { + try { + InputStream keyStoreInputStream; + File keyStoreFile = new File(filePath); + if (keyStoreFile.exists()) { + log.info("Reading key store from file {}", filePath); + keyStoreInputStream = new FileInputStream(keyStoreFile); + } else { + InputStream classPathStream = classLoader.getResourceAsStream(filePath); + if (classPathStream != null) { + log.info("Reading key store from class path {}", filePath); + keyStoreInputStream = classPathStream; + } else { + URI uri = Resources.getResource(filePath).toURI(); + log.info("Reading key store from URI {}", filePath); + keyStoreInputStream = new FileInputStream(new File(uri)); + } + } + return keyStoreInputStream; + } catch (Exception e) { + if (e instanceof NullPointerException) { + log.warn("Unable to find resource: " + filePath); + } else { + log.warn("Unable to find resource: " + filePath, e); + } + throw new RuntimeException("Unable to find resource: " + filePath); + } + } + + public static String getUri(Object classLoaderSource, String filePath) { + return getUri(classLoaderSource.getClass().getClassLoader(), filePath); + } + + public static String getUri(ClassLoader classLoader, String filePath) { + try { + File keyStoreFile = new File(filePath); + if (keyStoreFile.exists()) { + log.info("Reading key store from file {}", filePath); + return keyStoreFile.getAbsolutePath(); + } else { + URL url = classLoader.getResource(filePath); + return url.toURI().toString(); + } + } catch (Exception e) { + if (e instanceof NullPointerException) { + log.warn("Unable to find resource: " + filePath); + } else { + log.warn("Unable to find resource: " + filePath, e); + } + throw new RuntimeException("Unable to find resource: " + filePath); + } + } +} diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java index c5099069ee..f54c9b5a10 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java @@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.edge.exception.EdgeConnectionException; +import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.gen.edge.ConnectRequestMsg; import org.thingsboard.server.gen.edge.ConnectResponseCode; import org.thingsboard.server.gen.edge.ConnectResponseMsg; @@ -79,8 +80,8 @@ public class EdgeGrpcClient implements EdgeRpcClient { .keepAliveTime(keepAliveTimeSec, TimeUnit.SECONDS); if (sslEnabled) { try { - builder.sslContext(GrpcSslContexts.forClient().trustManager(new File(Resources.getResource(certResource).toURI())).build()); - } catch (URISyntaxException | SSLException e) { + builder.sslContext(GrpcSslContexts.forClient().trustManager(ResourceUtils.getInputStream(this, certResource)).build()); + } catch (SSLException e) { log.error("Failed to initialize channel!", e); throw new RuntimeException(e); } 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 54a9312eda..295b1eb9ba 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 @@ -20,15 +20,19 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.eclipse.leshan.server.model.LwM2mModelProvider; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.ResourceUtils; import javax.annotation.PostConstruct; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.InputStream; import java.net.URI; +import java.net.URISyntaxException; import java.security.KeyStore; @Slf4j @@ -139,29 +143,15 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @PostConstruct public void init() { - URI uri = null; try { - InputStream keyStoreInputStream; - File keyStoreFile = new File(keyStoreFilePath); - if (keyStoreFile.exists()) { - log.info("Reading key store from file {}", keyStoreFilePath); - keyStoreInputStream = new FileInputStream(keyStoreFile); - } else { - InputStream classPathStream = this.getClass().getClassLoader().getResourceAsStream(keyStoreFilePath); - if (classPathStream != null) { - log.info("Reading key store from class path {}", keyStoreFilePath); - keyStoreInputStream = classPathStream; - } else { - uri = Resources.getResource(keyStoreFilePath).toURI(); - log.info("Reading key store from URI {}", keyStoreFilePath); - keyStoreInputStream = new FileInputStream(new File(uri)); - } - } + InputStream keyStoreInputStream = ResourceUtils.getInputStream(this, keyStoreFilePath); keyStoreValue = KeyStore.getInstance(keyStoreType); keyStoreValue.load(keyStoreInputStream, keyStorePassword == null ? null : keyStorePassword.toCharArray()); } catch (Exception e) { - log.info("Unable to lookup LwM2M keystore. Reason: {}, {}", uri, e.getMessage()); + log.info("Unable to lookup LwM2M keystore. Reason: {}, {}", keyStoreFilePath, e.getMessage()); } } + + } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 2a0419b43d..7771509c37 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -25,6 +25,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.ResourceUtils; import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -74,20 +75,15 @@ public class MqttSslHandlerProvider { public SslHandler getSslHandler() { try { - URL ksUrl = Resources.getResource(keyStoreFile); - File ksFile = new File(ksUrl.toURI()); - URL tsUrl = Resources.getResource(keyStoreFile); - File tsFile = new File(tsUrl.toURI()); - TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance(keyStoreType); - try (InputStream tsFileInputStream = new FileInputStream(tsFile)) { + try (InputStream tsFileInputStream = ResourceUtils.getInputStream(this, keyStoreFile)) { trustStore.load(tsFileInputStream, keyStorePassword.toCharArray()); } tmFactory.init(trustStore); KeyStore ks = KeyStore.getInstance(keyStoreType); - try (InputStream ksFileInputStream = new FileInputStream(ksFile)) { + try (InputStream ksFileInputStream = ResourceUtils.getInputStream(this, keyStoreFile)) { ks.load(ksFileInputStream, keyStorePassword.toCharArray()); } KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); diff --git a/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java b/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java index 1abe47acd4..72de97e83b 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/MqttSslClient.java @@ -26,6 +26,7 @@ import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; +import org.thingsboard.server.common.data.ResourceUtils; import javax.net.ssl.*; import java.io.File; @@ -47,20 +48,15 @@ public class MqttSslClient { public static void main(String[] args) { try { - URL ksUrl = Resources.getResource(KEY_STORE_FILE); - File ksFile = new File(ksUrl.toURI()); - URL tsUrl = Resources.getResource(KEY_STORE_FILE); - File tsFile = new File(tsUrl.toURI()); - TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance(JKS); char[] ksPwd = new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x73, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; - trustStore.load(new FileInputStream(tsFile), ksPwd); + trustStore.load(ResourceUtils.getInputStream(MqttSslClient.class.getClassLoader(), KEY_STORE_FILE), ksPwd); tmf.init(trustStore); KeyStore ks = KeyStore.getInstance(JKS); - ks.load(new FileInputStream(ksFile), ksPwd); + ks.load(ResourceUtils.getInputStream(MqttSslClient.class.getClassLoader(), KEY_STORE_FILE), ksPwd); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); char[] clientPwd = new char[]{0x63, 0x6C, 0x69, 0x65, 0x6E, 0x74, 0x5F, 0x6B, 0x65, 0x79, 0x5F, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6F, 0x72, 0x64}; kmf.init(ks, clientPwd); From 2d9717f910b4196700ee69caa1a8c49a06c88058 Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 30 Jun 2021 19:10:36 +0300 Subject: [PATCH 187/231] Lwm2m: Opaque fix bug byte[] --- .../server/transport/lwm2m/server/client/LwM2mClient.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 a9af68160c..74edfed357 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 @@ -267,7 +267,7 @@ public class LwM2mClient implements Serializable { public String resourceToString(LwM2mResource resource, String key) { String value = null; StringBuilder builder = new StringBuilder(); - if (resource instanceof LwM2mSingleResource && ((byte[]) resource.getValue()).length > 0) { + if (resource instanceof LwM2mSingleResource) { builder.append("LwM2mSingleResource"); if (key == null) { builder.append(" id=").append(String.valueOf(resource.getId())); @@ -285,7 +285,9 @@ public class LwM2mClient implements Serializable { builder.append(" key=").append(key); } builder.append(" values={"); - builder.append(multiInstanceOpaqueToString((LwM2mMultipleResource) resource)); + if (resource.getInstances().size() > 0) { + builder.append(multiInstanceOpaqueToString((LwM2mMultipleResource) resource)); + } builder.append("}"); builder.append(" type=").append(OPAQUE.toString()); value = builder.toString(); From b2d694f7ee2ee9a8ba0d0a6d5b9b69406d3f513b Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 30 Jun 2021 19:30:39 +0300 Subject: [PATCH 188/231] Add separate SSL channel for mqtt transport --- .../src/main/resources/thingsboard.yml | 4 ++++ .../mqtt/MqttSslHandlerProvider.java | 9 ++++++++ .../mqtt/MqttTransportServerInitializer.java | 6 +++-- .../transport/mqtt/MqttTransportService.java | 22 ++++++++++++++++++- .../src/main/resources/tb-mqtt-transport.yml | 6 ++++- 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 79e8d9e1d3..2609be7d75 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -595,6 +595,10 @@ transport: ssl: # Enable/disable SSL support enabled: "${MQTT_SSL_ENABLED:false}" + # MQTT SSL bind address + bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" + # MQTT SSL bind port + bind_port: "${MQTT_SSL_BIND_PORT:8883}" # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" # Path to the key store that holds the SSL certificate diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 7771509c37..f61553bd22 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -73,7 +73,16 @@ public class MqttSslHandlerProvider { @Autowired private TransportService transportService; + private SslHandler sslHandler; + public SslHandler getSslHandler() { + if (sslHandler == null) { + sslHandler = createSslHandler(); + } + return sslHandler; + } + + private SslHandler createSslHandler() { try { TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance(keyStoreType); diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java index bbe382163b..386c2a8dd6 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java @@ -28,16 +28,18 @@ import io.netty.handler.ssl.SslHandler; public class MqttTransportServerInitializer extends ChannelInitializer { private final MqttTransportContext context; + private final boolean sslEnabled; - public MqttTransportServerInitializer(MqttTransportContext context) { + public MqttTransportServerInitializer(MqttTransportContext context, boolean sslEnabled) { this.context = context; + this.sslEnabled = sslEnabled; } @Override public void initChannel(SocketChannel ch) { ChannelPipeline pipeline = ch.pipeline(); SslHandler sslHandler = null; - if (context.getSslHandlerProvider() != null) { + if (sslEnabled && context.getSslHandlerProvider() != null) { sslHandler = context.getSslHandlerProvider().getSslHandler(); pipeline.addLast(sslHandler); } 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 3e53dec227..5498752151 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 @@ -46,6 +46,14 @@ public class MqttTransportService implements TbTransportService { @Value("${transport.mqtt.bind_port}") private Integer port; + @Value("${transport.mqtt.ssl.enabled}") + private boolean sslEnabled; + + @Value("${transport.mqtt.ssl.bind_address}") + private String sslHost; + @Value("${transport.mqtt.ssl.bind_port}") + private Integer sslPort; + @Value("${transport.mqtt.netty.leak_detector_level}") private String leakDetectorLevel; @Value("${transport.mqtt.netty.boss_group_thread_count}") @@ -59,6 +67,7 @@ public class MqttTransportService implements TbTransportService { private MqttTransportContext context; private Channel serverChannel; + private Channel sslServerChannel; private EventLoopGroup bossGroup; private EventLoopGroup workerGroup; @@ -73,10 +82,18 @@ public class MqttTransportService implements TbTransportService { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) - .childHandler(new MqttTransportServerInitializer(context)) + .childHandler(new MqttTransportServerInitializer(context, false)) .childOption(ChannelOption.SO_KEEPALIVE, keepAlive); serverChannel = b.bind(host, port).sync().channel(); + if (sslEnabled) { + b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new MqttTransportServerInitializer(context, true)) + .childOption(ChannelOption.SO_KEEPALIVE, keepAlive); + sslServerChannel = b.bind(sslHost, sslPort).sync().channel(); + } log.info("Mqtt transport started!"); } @@ -85,6 +102,9 @@ public class MqttTransportService implements TbTransportService { log.info("Stopping MQTT transport!"); try { serverChannel.close().sync(); + if (sslEnabled) { + sslServerChannel.close().sync(); + } } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index 6dbbb99311..f748bd872d 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -99,6 +99,10 @@ transport: ssl: # Enable/disable SSL support enabled: "${MQTT_SSL_ENABLED:false}" + # MQTT SSL bind address + bind_address: "${MQTT_SSL_BIND_ADDRESS:0.0.0.0}" + # MQTT SSL bind port + bind_port: "${MQTT_SSL_BIND_PORT:8883}" # SSL protocol: See http://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#SSLContext protocol: "${MQTT_SSL_PROTOCOL:TLSv1.2}" # Path to the key store that holds the SSL certificate @@ -298,4 +302,4 @@ management: web: exposure: # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics). - include: '${METRICS_ENDPOINTS_EXPOSE:info}' \ No newline at end of file + include: '${METRICS_ENDPOINTS_EXPOSE:info}' From b3dc441428205bc158b1d3e84d6803c078ee7356 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 30 Jun 2021 20:03:21 +0300 Subject: [PATCH 189/231] Fix io.netty.handler.ssl.SslHandler is not a @Sharable handler --- .../mqtt/MqttSslHandlerProvider.java | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index f61553bd22..7db6f0dff0 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -15,12 +15,10 @@ */ package org.thingsboard.server.transport.mqtt; -import com.google.common.io.Resources; import io.netty.handler.ssl.SslHandler; 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.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -30,8 +28,8 @@ 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.gen.transport.TransportProtos; import org.thingsboard.server.common.transport.util.SslUtil; +import org.thingsboard.server.gen.transport.TransportProtos; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; @@ -40,10 +38,7 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import java.io.File; -import java.io.FileInputStream; import java.io.InputStream; -import java.net.URL; import java.security.KeyStore; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; @@ -73,16 +68,16 @@ public class MqttSslHandlerProvider { @Autowired private TransportService transportService; - private SslHandler sslHandler; + private SSLEngine sslEngine; public SslHandler getSslHandler() { - if (sslHandler == null) { - sslHandler = createSslHandler(); + if (sslEngine == null) { + sslEngine = createSslEngine(); } - return sslHandler; + return new SslHandler(sslEngine); } - private SslHandler createSslHandler() { + private SSLEngine createSslEngine() { try { TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance(keyStoreType); @@ -113,10 +108,10 @@ public class MqttSslHandlerProvider { sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols()); sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites()); sslEngine.setEnableSessionCreation(true); - return new SslHandler(sslEngine); + return sslEngine; } catch (Exception e) { log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e); - throw new RuntimeException("Failed to get SSL handler", e); + throw new RuntimeException("Failed to get SSL engine", e); } } From a230e5838b8db0f42136e609020653183f60b0a6 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 30 Jun 2021 20:32:50 +0300 Subject: [PATCH 190/231] Use single SSL context for mqtt transport --- .../mqtt/MqttSslHandlerProvider.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java index 7db6f0dff0..fad743aec2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java @@ -68,16 +68,23 @@ public class MqttSslHandlerProvider { @Autowired private TransportService transportService; - private SSLEngine sslEngine; + private SSLContext sslContext; public SslHandler getSslHandler() { - if (sslEngine == null) { - sslEngine = createSslEngine(); + if (sslContext == null) { + sslContext = createSslContext(); } + SSLEngine sslEngine = sslContext.createSSLEngine(); + sslEngine.setUseClientMode(false); + sslEngine.setNeedClientAuth(false); + sslEngine.setWantClientAuth(true); + sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols()); + sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites()); + sslEngine.setEnableSessionCreation(true); return new SslHandler(sslEngine); } - private SSLEngine createSslEngine() { + private SSLContext createSslContext() { try { TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); KeyStore trustStore = KeyStore.getInstance(keyStoreType); @@ -101,17 +108,10 @@ public class MqttSslHandlerProvider { } SSLContext sslContext = SSLContext.getInstance(sslProtocol); sslContext.init(km, tm, null); - SSLEngine sslEngine = sslContext.createSSLEngine(); - sslEngine.setUseClientMode(false); - sslEngine.setNeedClientAuth(false); - sslEngine.setWantClientAuth(true); - sslEngine.setEnabledProtocols(sslEngine.getSupportedProtocols()); - sslEngine.setEnabledCipherSuites(sslEngine.getSupportedCipherSuites()); - sslEngine.setEnableSessionCreation(true); - return sslEngine; + return sslContext; } catch (Exception e) { log.error("Unable to set up SSL context. Reason: " + e.getMessage(), e); - throw new RuntimeException("Failed to get SSL engine", e); + throw new RuntimeException("Failed to get SSL context", e); } } From 321ccd5722f9327513453db7af383fbaec5b66c7 Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Wed, 30 Jun 2021 23:59:34 +0300 Subject: [PATCH 191/231] UI: Fixed generate invalid name widgetTypeFunctionBody --- .../modules/home/components/widget/widget-component.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts index d714b96f4b..7ddcaa0764 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts @@ -384,7 +384,7 @@ export class WidgetComponentService { } private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor { - let widgetTypeFunctionBody = `return function ${name} (ctx) {\n` + + let widgetTypeFunctionBody = `return function _${name} (ctx) {\n` + ' var self = this;\n' + ' self.ctx = ctx;\n\n'; /*+ From 385c60a6565b56b39bc62af7b440d5926c822eda Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 30 Jun 2021 18:52:15 +0300 Subject: [PATCH 192/231] UI: LwM2M device profile refactoring object list component models data --- ...ofile-transport-configuration.component.ts | 3 +- ...ofile-transport-configuration.component.ts | 42 +++++++------------ .../lwm2m/lwm2m-object-list.component.ts | 20 ++++----- .../lwm2m/lwm2m-profile-config.models.ts | 5 --- 4 files changed, 24 insertions(+), 46 deletions(-) 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 f5eb23e9e3..7d74cbc387 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,8 @@ export class DeviceProfileTransportConfigurationComponent implements ControlValu if (configuration) { delete configuration.type; } - this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); setTimeout(() => { - this.deviceProfileTransportConfigurationFormGroup.updateValueAndValidity(); + this.deviceProfileTransportConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); }, 0); } 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 777431ea3a..5edcf84345 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 @@ -38,7 +38,6 @@ import { INSTANCES, KEY_NAME, Lwm2mProfileConfigModels, - ModelValue, ObjectLwM2M, OBSERVE, OBSERVE_ATTR_TELEMETRY, @@ -49,7 +48,7 @@ import { TELEMETRY } from './lwm2m-profile-config.models'; import { DeviceProfileService } from '@core/http/device-profile.service'; -import { deepClone, isDefinedAndNotNull, isEmpty, isUndefined } from '@core/utils'; +import { deepClone, isDefinedAndNotNull, isEmpty } 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'; @@ -226,32 +225,30 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } private initWriteValue = (): void => { - const modelValue = {objectIds: [], objectsList: []} as ModelValue; - modelValue.objectIds = this.getObjectsFromJsonAllConfig(); - if (modelValue.objectIds.length > 0) { + const objectIds = this.getObjectsFromJsonAllConfig(); + if (objectIds.length > 0) { const sortOrder = { property: 'id', direction: Direction.ASC }; - this.deviceProfileService.getLwm2mObjects(sortOrder, modelValue.objectIds, null).subscribe( + this.deviceProfileService.getLwm2mObjects(sortOrder, objectIds, null).subscribe( (objectsList) => { - modelValue.objectsList = objectsList; - this.updateWriteValue(modelValue); + this.updateWriteValue(objectsList); } ); } else { - this.updateWriteValue(modelValue); + this.updateWriteValue([]); } } - private updateWriteValue = (value: ModelValue): void => { + private updateWriteValue = (value: ObjectLwM2M[]): void => { const fwResource = isDefinedAndNotNull(this.configurationValue.clientLwM2mSettings.fwUpdateResource) ? this.configurationValue.clientLwM2mSettings.fwUpdateResource : ''; const swResource = isDefinedAndNotNull(this.configurationValue.clientLwM2mSettings.swUpdateResource) ? this.configurationValue.clientLwM2mSettings.swUpdateResource : ''; this.lwm2mDeviceProfileFormGroup.patchValue({ objectIds: value, - observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value.objectsList), + observeAttrTelemetry: this.getObserveAttrTelemetryObjects(value), bootstrap: this.configurationValue.bootstrap, clientLwM2mSettings: { clientOnlyObserveAfterConnect: this.configurationValue.clientLwM2mSettings.clientOnlyObserveAfterConnect, @@ -334,6 +331,7 @@ 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/')) @@ -454,21 +452,13 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro }); } }); - if (isUndefined(this.configurationValue.observeAttr)) { - this.configurationValue.observeAttr = { - observe: observeArray, - attribute: attributeArray, - telemetry: telemetryArray, - keyName: this.sortObjectKeyPathJson(KEY_NAME, keyNameNew), - attributeLwm2m: attributes - }; - } else { - this.configurationValue.observeAttr.observe = observeArray; - this.configurationValue.observeAttr.attribute = attributeArray; - this.configurationValue.observeAttr.telemetry = telemetryArray; - this.configurationValue.observeAttr.keyName = this.sortObjectKeyPathJson(KEY_NAME, keyNameNew); - this.configurationValue.observeAttr.attributeLwm2m = attributes; - } + this.configurationValue.observeAttr = { + observe: observeArray, + attribute: attributeArray, + telemetry: telemetryArray, + keyName: this.sortObjectKeyPathJson(KEY_NAME, keyNameNew), + attributeLwm2m: attributes + }; } sortObjectKeyPathJson = (key: string, value: object): object => { 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 62d96b69e0..c3a0e1e4b3 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 @@ -28,7 +28,7 @@ import { import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { Observable } from 'rxjs'; import { distinctUntilChanged, filter, mergeMap, share, tap } from 'rxjs/operators'; -import { ModelValue, ObjectLwM2M, PAGE_SIZE_LIMIT } from './lwm2m-profile-config.models'; +import { 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'; @@ -54,7 +54,6 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V private requiredValue: boolean; private dirty = false; - private modelValue: Array = []; lwm2mListFormGroup: FormGroup; objectsList: Array = []; @@ -92,7 +91,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V this.lwm2mListFormGroup.valueChanges.subscribe((value) => { let formValue = null; if (this.lwm2mListFormGroup.valid) { - formValue = value; + formValue = value.objectsList; } this.propagateChange(formValue); }); @@ -139,15 +138,13 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } } - writeValue(value: ModelValue): void { + writeValue(value: ObjectLwM2M[]): void { this.searchText = ''; if (isDefinedAndNotNull(value)) { - if (Array.isArray(value.objectIds)) { - this.modelValue = value.objectIds; - this.objectsList = value.objectsList; + if (Array.isArray(value)) { + this.objectsList = value; } else { this.objectsList = []; - this.modelValue = []; } this.lwm2mListFormGroup.patchValue({objectsList: this.objectsList}, {emitEvent: false}); this.dirty = false; @@ -161,8 +158,7 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } private add(object: ObjectLwM2M): void { - if (isDefinedAndNotNull(this.modelValue) && this.modelValue.indexOf(object.keyId) === -1) { - this.modelValue.push(object.keyId); + if (isDefinedAndNotNull(this.objectsList) && this.objectsList.findIndex(item => item.keyId === object.keyId) === -1) { this.objectsList.push(object); this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList); this.addList.next(this.objectsList); @@ -171,12 +167,10 @@ export class Lwm2mObjectListComponent implements ControlValueAccessor, OnInit, V } remove = (object: ObjectLwM2M): void => { - let index = this.objectsList.indexOf(object); + const index = this.objectsList.indexOf(object); if (index >= 0) { this.objectsList.splice(index, 1); this.lwm2mListFormGroup.get('objectsList').setValue(this.objectsList); - index = this.modelValue.indexOf(object.keyId); - this.modelValue.splice(index, 1); this.removeList.next(object); this.clear(); } 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 b550e4f5ae..a6bfd39eb6 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,11 +127,6 @@ export const PowerModeTranslationMap = new Map( ] ); -export interface ModelValue { - objectIds: string[]; - objectsList: ObjectLwM2M[]; -} - export interface BootstrapServersSecurityConfig { shortId: number; lifetime: number; From 8e78b65e772afa36e8ce46b1e9ff6ac31612af90 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 30 Jun 2021 19:07:27 +0300 Subject: [PATCH 193/231] UI: LwM2M device profile typo data model --- .../lwm2m-device-profile-transport-configuration.component.ts | 3 +-- .../profile/device/lwm2m/lwm2m-profile-config.models.ts | 1 - 2 files changed, 1 insertion(+), 3 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 5edcf84345..bb9c3c87f6 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 @@ -40,7 +40,6 @@ import { Lwm2mProfileConfigModels, ObjectLwM2M, OBSERVE, - OBSERVE_ATTR_TELEMETRY, PowerMode, PowerModeTranslationMap, RESOURCES, @@ -517,7 +516,7 @@ export class Lwm2mDeviceProfileTransportConfigurationComponent implements Contro } removeObjectsList = (value: ObjectLwM2M): void => { - const objectsOld = this.lwm2mDeviceProfileFormGroup.get(OBSERVE_ATTR_TELEMETRY).value.clientLwM2M; + const objectsOld = this.lwm2mDeviceProfileFormGroup.get('observeAttrTelemetry').value; const isIdIndex = (element) => element.keyId === value.keyId; const index = objectsOld.findIndex(isIdIndex); if (index >= 0) { 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 a6bfd39eb6..d9c2eb87ca 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 @@ -20,7 +20,6 @@ export const PAGE_SIZE_LIMIT = 50; export const INSTANCES = 'instances'; export const INSTANCE = 'instance'; export const RESOURCES = 'resources'; -export const OBSERVE_ATTR_TELEMETRY = 'observeAttrTelemetry'; export const OBSERVE = 'observe'; export const ATTRIBUTE = 'attribute'; export const TELEMETRY = 'telemetry'; From bd558ed4c68cc63f62266ae107ffc0afca8aebc5 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 30 Jun 2021 19:41:22 +0300 Subject: [PATCH 194/231] UI: LwM2M device profile refactoring select attributes and telemetry --- ...serve-attr-telemetry-instances.component.html | 5 ++++- ...observe-attr-telemetry-instances.component.ts | 16 ++++++++++++++++ ...observe-attr-telemetry-resources.component.ts | 4 ++-- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.html index e9e805177f..2ccfd45616 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.html @@ -32,6 +32,7 @@ @@ -40,14 +41,16 @@
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.ts index 04528e4abc..adf0d60db0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-instances.component.ts @@ -161,6 +161,13 @@ export class Lwm2mObserveAttrTelemetryInstancesComponent implements ControlValue const resources = deepClone(instance.get('resources').value as ResourceLwM2M[]); if (value && type === 'observe') { resources.forEach(resource => resource[type] = resource.telemetry || resource.attribute); + } else if (!value && type !== 'observe') { + resources.forEach(resource => { + resource[type] = value; + if (resource.observe && !(resource.telemetry || resource.attribute)) { + resource.observe = false; + } + }); } else { resources.forEach(resource => resource[type] = value); } @@ -190,6 +197,15 @@ export class Lwm2mObserveAttrTelemetryInstancesComponent implements ControlValue return isDefinedAndNotNull(resources) && resources.every(resource => resource[type]); } + disableObserve(instance: AbstractControl): boolean { + return this.disabled || !( + this.getIndeterminate(instance, 'telemetry') || + this.getIndeterminate(instance, 'attribute') || + this.getChecked(instance, 'telemetry') || + this.getChecked(instance, 'attribute') + ); + } + get isExpend(): boolean { return this.instancesFormArray.length === 1; } diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts index bced631596..8af08d8f36 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts @@ -129,7 +129,7 @@ export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValue private updatedResources(resources: ResourceLwM2M[]): void { if (resources.length === this.resourcesFormArray.length) { - this.resourcesFormArray.patchValue(resources, {emitEvent: false}); + this.resourcesFormArray.patchValue(resources, {onlySelf: true}); } else { if (this.valueChange$) { this.valueChange$.unsubscribe(); @@ -144,7 +144,7 @@ export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValue if (this.disabled) { this.resourcesFormGroup.disable({emitEvent: false}); } - this.valueChange$ = this.resourcesFormGroup.valueChanges.subscribe(value => { + this.valueChange$ = this.resourcesFormGroup.valueChanges.subscribe(() => { this.updateModel(this.resourcesFormGroup.getRawValue().resources); }); } From 194200da49d08cf4ad3d370716d8b969df4fe6e1 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 1 Jul 2021 10:44:48 +0300 Subject: [PATCH 195/231] UI: LwM2M device profile resource fixed disabled observe checkbox --- ...m-observe-attr-telemetry-resources.component.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts index 8af08d8f36..bca6d927d9 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts @@ -164,12 +164,14 @@ export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValue form.get('attribute').valueChanges.pipe(startWith(resource.attribute), takeUntil(this.destroy$)), form.get('telemetry').valueChanges.pipe(startWith(resource.telemetry), takeUntil(this.destroy$)) ]).subscribe(([attribute, telemetry]) => { - if (attribute || telemetry) { - form.get('observe').enable({emitEvent: false}); - } else { - form.get('observe').disable({emitEvent: false}); - form.get('observe').patchValue(false, {emitEvent: false}); - form.get('attributes').patchValue({}, {emitEvent: false}); + if (!this.disabled) { + if (attribute || telemetry) { + form.get('observe').enable({emitEvent: false}); + } else { + form.get('observe').disable({emitEvent: false}); + form.get('observe').patchValue(false, {emitEvent: false}); + form.get('attributes').patchValue({}, {emitEvent: false}); + } } }); return form; From 31a86d0555d31fbd4a674dee24ec4b8d73c40ace Mon Sep 17 00:00:00 2001 From: nickAS21 Date: Wed, 30 Jun 2021 18:45:20 +0300 Subject: [PATCH 196/231] Lwm2m: Write Composite --- .../DefaultLwM2mDownlinkMsgHandler.java | 35 ++++++++++--------- .../downlink/LwM2mDownlinkMsgHandler.java | 7 ++-- .../rpc/DefaultLwM2MRpcRequestHandler.java | 25 ++++++------- 3 files changed, 33 insertions(+), 34 deletions(-) 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 1599d73104..e0dea149e5 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 @@ -61,9 +61,8 @@ 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.downlink.composite.TbLwM2MReadCompositeRequest; -import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MWriteResponseCompositeCallback; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest; import org.thingsboard.server.transport.lwm2m.utils.LwM2mValueConverterImpl; import javax.annotation.PostConstruct; @@ -73,7 +72,6 @@ import java.util.Collection; import java.util.Date; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; @@ -136,19 +134,6 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im sendCompositeRequest(client, downlink, this.config.getTimeout(), callback); } - @Override - public void sendWriteCompositeRequest(LwM2mClient client, Map nodes, DefaultLwM2MUplinkMsgHandler handler) { -// ResourceModel resourceModelWrite = client.getResourceModel(request.getVersionedId(), this.config.getModelProvider()); - TbLwM2MWriteResponseCompositeCallback callback = new TbLwM2MWriteResponseCompositeCallback(handler, logService, client, null); - ContentFormat contentFormat = ContentFormat.SENML_JSON; - try { - WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormat, nodes); - sendWriteCompositeRequest(client, downlink, this.config.getTimeout(), callback); - } catch (Exception e) { - callback.onError(JacksonUtil.toString(nodes), e); - } - } - @Override public void sendObserveRequest(LwM2mClient client, TbLwM2MObserveRequest request, DownlinkRequestCallback callback) { validateVersionedId(client, request); @@ -258,6 +243,17 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im } } + @Override + public void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest rpcWriteCompositeRequest, DownlinkRequestCallback callback) { + ContentFormat contentFormat = ContentFormat.SENML_JSON; + try { + WriteCompositeRequest downlink = new WriteCompositeRequest(contentFormat, rpcWriteCompositeRequest.getNodes()); + sendWriteCompositeRequest(client, downlink, this.config.getTimeout(), callback); + } catch (Exception e) { + callback.onError(JacksonUtil.toString(rpcWriteCompositeRequest), e); + } + } + @Override public void sendWriteUpdateRequest(LwM2mClient client, TbLwM2MWriteUpdateRequest request, DownlinkRequestCallback callback) { LwM2mPath resultIds = new LwM2mPath(request.getObjectId()); @@ -330,7 +326,12 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im context.getServer().send(registration, request, timeoutInMs, response -> { executor.submit(() -> { try { - callback.onSuccess(request, response); + if (response.isSuccess()) { + callback.onSuccess(request, response); + } + else { + callback.onValidationError(request.getNodes().values().toString(), response.getErrorMessage()); + } } catch (Exception e) { log.error("[{}] failed to process successful response [{}] ", registration.getEndpoint(), response, 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 04c63aec17..871a59a63b 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 @@ -23,6 +23,7 @@ import org.eclipse.leshan.core.request.ObserveRequest; import org.eclipse.leshan.core.request.ReadCompositeRequest; import org.eclipse.leshan.core.request.ReadRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; +import org.eclipse.leshan.core.request.WriteCompositeRequest; import org.eclipse.leshan.core.request.WriteRequest; import org.eclipse.leshan.core.response.DeleteResponse; import org.eclipse.leshan.core.response.DiscoverResponse; @@ -31,13 +32,13 @@ import org.eclipse.leshan.core.response.ObserveResponse; import org.eclipse.leshan.core.response.ReadCompositeResponse; import org.eclipse.leshan.core.response.ReadResponse; import org.eclipse.leshan.core.response.WriteAttributesResponse; +import org.eclipse.leshan.core.response.WriteCompositeResponse; import org.eclipse.leshan.core.response.WriteResponse; import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClient; import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MReadCompositeRequest; -import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2MUplinkMsgHandler; +import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcWriteCompositeRequest; import java.util.List; -import java.util.Map; import java.util.Set; public interface LwM2mDownlinkMsgHandler { @@ -66,7 +67,7 @@ public interface LwM2mDownlinkMsgHandler { void sendWriteReplaceRequest(LwM2mClient client, TbLwM2MWriteReplaceRequest request, DownlinkRequestCallback callback); - void sendWriteCompositeRequest(LwM2mClient client, Map nodes, DefaultLwM2MUplinkMsgHandler handler); + void sendWriteCompositeRequest(LwM2mClient client, RpcWriteCompositeRequest nodes, 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/rpc/DefaultLwM2MRpcRequestHandler.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/DefaultLwM2MRpcRequestHandler.java index ee195dbbe3..147a8a5131 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 @@ -52,6 +52,7 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteRespon import org.thingsboard.server.transport.lwm2m.server.downlink.TbLwM2MWriteUpdateRequest; import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MReadCompositeCallback; import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MReadCompositeRequest; +import org.thingsboard.server.transport.lwm2m.server.downlink.composite.TbLwM2MWriteResponseCompositeCallback; import org.thingsboard.server.transport.lwm2m.server.log.LwM2MTelemetryLogService; import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcReadCompositeRequest; import org.thingsboard.server.transport.lwm2m.server.rpc.composite.RpcReadResponseCompositeCallback; @@ -247,22 +248,18 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { } /** - * Map nodes = new HashMap<>(); - * nodes.put("/3/0/14", "+02"); - * nodes.put("/1/0/2", 100); - * nodes.put("/5/0/1", "coap://localhost:5685"); - * // defaultLwM2MDownlinkMsgHandler.sendWriteCompositeRequest(lwM2MClient, nodes, this); - * @param client - * @param requestMsg + * WriteComposite {"nodes":{"/3/0/14":"+04", "/1/0/2":100, "/5/0/1":"coap://localhost:5685"}} + * {"result":"CHANGED"} + * Map nodes = new HashMap<>(); + * nodes.put("/3/0/14", "+02"); + * nodes.put("/1/0/2", 100); + * nodes.put("/5/0/1", "coap://localhost:5685"); */ private void sendWriteCompositeRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg) { - RpcWriteCompositeRequest nodes = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class); -// TbLwM2MWriteReplaceRequest request = TbLwM2MWriteReplaceRequest.builder().versionedId(versionedId) -// .value(requestBody.getValue()) -// .timeout(this.config.getTimeout()).build(); -// var mainCallback = new TbLwM2MWriteResponseCallback(uplinkHandler, logService, client, versionedId); -// var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); -// downlinkHandler.sendWriteReplaceRequest(client, request, rpcCallback); + RpcWriteCompositeRequest rpcWriteCompositeRequest = JacksonUtil.fromString(requestMsg.getParams(), RpcWriteCompositeRequest.class); + var mainCallback = new TbLwM2MWriteResponseCompositeCallback(uplinkHandler, logService, client, null); + var rpcCallback = new RpcEmptyResponseCallback<>(transportService, client, requestMsg, mainCallback); + downlinkHandler.sendWriteCompositeRequest(client, rpcWriteCompositeRequest, rpcCallback); } private void sendCancelObserveRequest(LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, String versionedId) { From a482a30198cd9a5e37f8de590e301bb9b7005f58 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 1 Jul 2021 13:42:03 +0300 Subject: [PATCH 197/231] Merge Opaque resource conversion to String --- .../lwm2m/server/LwM2mTransportUtil.java | 79 +++++++++++++++++++ .../lwm2m/server/client/LwM2mClient.java | 71 ----------------- .../server/client/LwM2mClientContext.java | 2 + .../server/client/LwM2mClientContextImpl.java | 7 ++ .../DefaultLwM2mDownlinkMsgHandler.java | 3 +- .../rpc/DefaultLwM2MRpcRequestHandler.java | 34 ++++---- .../server/rpc/RpcReadResponseCallback.java | 4 +- .../RpcReadResponseCompositeCallback.java | 4 +- 8 files changed, 111 insertions(+), 93 deletions(-) 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 5930825894..6fb5fd195e 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 @@ -22,8 +22,10 @@ import org.eclipse.leshan.core.attributes.Attribute; import org.eclipse.leshan.core.attributes.AttributeSet; 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.LwM2mPath; import org.eclipse.leshan.core.node.LwM2mResource; +import org.eclipse.leshan.core.node.LwM2mSingleResource; import org.eclipse.leshan.core.node.codec.CodecException; import org.eclipse.leshan.core.request.SimpleDownlinkRequest; import org.eclipse.leshan.core.request.WriteAttributesRequest; @@ -48,6 +50,7 @@ import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import static org.eclipse.leshan.core.attributes.Attribute.DIMENSION; @@ -371,4 +374,80 @@ public class LwM2mTransportUtil { return lwm2mResourceValue; } + public static Optional contentToString(Object content) { + try { + String value = null; + LwM2mResource resource = null; + String key = null; + if (content instanceof Map) { + Map contentAsMap = (Map) content; + if (contentAsMap.size() == 1) { + for (Map.Entry kv : contentAsMap.entrySet()) { + if (kv.getValue() instanceof LwM2mResource) { + key = kv.getKey().toString(); + resource = (LwM2mResource) kv.getValue(); + } + } + } + } else if (content instanceof LwM2mResource) { + resource = (LwM2mResource) content; + } + if (resource != null && resource.getType() == OPAQUE) { + value = opaqueResourceToString(resource, key); + } + value = value == null ? content.toString() : value; + return Optional.of(value); + } catch (Exception e) { + log.debug("Failed to convert content " + content + " to string", e); + return Optional.ofNullable(content != null ? content.toString() : null); + } + } + + private static String opaqueResourceToString(LwM2mResource resource, String key) { + String value = null; + StringBuilder builder = new StringBuilder(); + if (resource instanceof LwM2mSingleResource) { + builder.append("LwM2mSingleResource"); + if (key == null) { + builder.append(" id=").append(String.valueOf(resource.getId())); + } else { + builder.append(" key=").append(key); + } + builder.append(" value=").append(opaqueToString((byte[]) resource.getValue())); + builder.append(" type=").append(OPAQUE.toString()); + value = builder.toString(); + } else if (resource instanceof LwM2mMultipleResource) { + builder.append("LwM2mMultipleResource"); + if (key == null) { + builder.append(" id=").append(String.valueOf(resource.getId())); + } else { + builder.append(" key=").append(key); + } + builder.append(" values={"); + if (resource.getInstances().size() > 0) { + builder.append(multiInstanceOpaqueToString((LwM2mMultipleResource) resource)); + } + builder.append("}"); + builder.append(" type=").append(OPAQUE.toString()); + value = builder.toString(); + } + return value; + } + + private static String multiInstanceOpaqueToString(LwM2mMultipleResource resource) { + StringBuilder builder = new StringBuilder(); + resource.getInstances().values() + .forEach(v -> builder.append(" id=").append(v.getId()).append(" value=").append(Hex.encodeHexString((byte[]) v.getValue())).append(", ")); + int startInd = builder.lastIndexOf(", "); + if (startInd > 0) { + builder.delete(startInd, startInd + 2); + } + return builder.toString(); + } + + private static String opaqueToString(byte[] value) { + String opaque = Hex.encodeHexString(value); + return opaque.length() > 1024 ? opaque.substring(0, 1024) : opaque; + } + } 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 74edfed357..d88e0131b1 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 @@ -243,74 +243,7 @@ public class LwM2mClient implements Serializable { .getObjectModel(pathIds.getObjectId()) : null; } - public Optional contentToString(Object content) { - String value = null; - LwM2mResource resource = null; - String key = null; - if (content instanceof HashMap && ((HashMap) content).size() == 1) { - key = ((HashMap) content).keySet().toArray()[0].toString(); - if (((HashMap) content).values().toArray()[0] != null) { - if (((HashMap) content).values().toArray()[0] instanceof LwM2mResource) { - resource = (LwM2mResource) ((HashMap) content).values().toArray()[0]; - } - } - } else if (content instanceof LwM2mResource) { - resource = (LwM2mResource) content; - } - if (resource != null && resource.getType() == OPAQUE) { - value = this.resourceToString(resource, key); - } - value = value == null ? content.toString() : value; - return Optional.of(String.format("%s", value)); - } - - public String resourceToString(LwM2mResource resource, String key) { - String value = null; - StringBuilder builder = new StringBuilder(); - if (resource instanceof LwM2mSingleResource) { - builder.append("LwM2mSingleResource"); - if (key == null) { - builder.append(" id=").append(String.valueOf(resource.getId())); - } else { - builder.append(" key=").append(key); - } - builder.append(" value=").append(opaqueToString((byte[]) resource.getValue())); - builder.append(" type=").append(OPAQUE.toString()); - value = builder.toString(); - } else if (resource instanceof LwM2mMultipleResource) { - builder.append("LwM2mMultipleResource"); - if (key == null) { - builder.append(" id=").append(String.valueOf(resource.getId())); - } else { - builder.append(" key=").append(key); - } - builder.append(" values={"); - if (resource.getInstances().size() > 0) { - builder.append(multiInstanceOpaqueToString((LwM2mMultipleResource) resource)); - } - builder.append("}"); - builder.append(" type=").append(OPAQUE.toString()); - value = builder.toString(); - } - return value; - } - private String multiInstanceOpaqueToString(LwM2mMultipleResource resource) { - StringBuilder builder = new StringBuilder(); - resource.getInstances().values().stream().map(v -> { - return builder.append(" id=").append(v.getId()).append(" value=").append(Hex.encodeHexString((byte[]) v.getValue())).append(", "); - }); - int startInd = builder.lastIndexOf(", "); - if (startInd > 0) { - builder.delete(startInd, startInd + 2); - } - return builder.toString(); - } - - private String opaqueToString(byte[] value) { - String opaque = Hex.encodeHexString(value); - return opaque.length() > 1024 ? opaque.substring(0, 1024) : opaque; - } public Collection getNewResourceForInstance(String pathRezIdVer, Object params, LwM2mModelProvider modelProvider, LwM2mValueConverter converter) { @@ -407,9 +340,5 @@ public class LwM2mClient implements Serializable { this.lock = new ReentrantLock(); } - public boolean isComposite (LwM2mClientContext clientContext) { - return LwM2mVersion.fromVersionStr(registration.getLwM2mVersion()).isComposite() & - clientContext.getProfile(this.profileId).getClientLwM2mSettings().isCompositeOperationsSupport(); - } } 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 a7305177e1..643ab61737 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 @@ -60,4 +60,6 @@ public interface LwM2mClientContext { void removeCredentials(TransportProtos.SessionInfoProto sessionInfo); void sendMsgsAfterSleeping(LwM2mClient lwM2MClient); + + boolean isComposite(LwM2mClient client); } 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 04c4cc6bcd..b861ce4cd0 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,6 +35,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbLwM2mTransportComponent; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; +import org.thingsboard.server.transport.lwm2m.config.LwM2mVersion; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil; @@ -339,6 +340,12 @@ public class LwM2mClientContextImpl implements LwM2mClientContext { return lwM2mClientsByRegistrationId.values().stream().filter(e -> deviceId.equals(e.getDeviceId())).findFirst().orElse(null); } + @Override + public boolean isComposite(LwM2mClient client) { + return LwM2mVersion.fromVersionStr(client.getRegistration().getLwM2mVersion()).isComposite() & + getProfile(client.getProfileId()).getClientLwM2mSettings().isCompositeOperationsSupport(); + } + private boolean validateResourceInModel(LwM2mClient lwM2mClient, String pathIdVer, boolean isWritableNotOptional) { ResourceModel resourceModel = lwM2mClient.getResourceModel(pathIdVer, this.config .getModelProvider()); 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 e0dea149e5..619cbdd7a0 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 @@ -328,8 +328,7 @@ public class DefaultLwM2mDownlinkMsgHandler extends LwM2MExecutorAwareService im try { if (response.isSuccess()) { callback.onSuccess(request, response); - } - else { + } else { callback.onValidationError(request.getNodes().values().toString(), response.getErrorMessage()); } } catch (Exception e) { 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 147a8a5131..7954cdda98 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 @@ -98,7 +98,7 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { return; } try { - if (operationType.isHasObjectId()) { + if (operationType.isHasObjectId()) { String objectId = getIdFromParameters(client, rpcRequst); switch (operationType) { case READ: @@ -132,23 +132,21 @@ public class DefaultLwM2MRpcRequestHandler implements LwM2MRpcRequestHandler { throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); } } else if (operationType.isComposite()) { - if (client.isComposite(clientContext)) { - switch (operationType) { - case READ_COMPOSITE: - sendReadCompositeRequest(client, rpcRequst); - break; - case WRITE_COMPOSITE: - sendWriteCompositeRequest(client, rpcRequst); - break; - default: - throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); - } - } - else { - this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), - ResponseCode.INTERNAL_SERVER_ERROR.getName(), "This device does not support Composite Operation"); - return; - } + if (clientContext.isComposite(client)) { + switch (operationType) { + case READ_COMPOSITE: + sendReadCompositeRequest(client, rpcRequst); + break; + case WRITE_COMPOSITE: + sendWriteCompositeRequest(client, rpcRequst); + break; + default: + throw new IllegalArgumentException("Unsupported operation: " + operationType.name()); + } + } else { + this.sendErrorRpcResponse(sessionInfo, rpcRequst.getRequestId(), + ResponseCode.INTERNAL_SERVER_ERROR.getName(), "This device does not support Composite Operation"); + } } else { switch (operationType) { case OBSERVE_CANCEL_ALL: 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 3a53ef5d5d..0a5fa70b5b 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 @@ -24,6 +24,8 @@ import org.thingsboard.server.transport.lwm2m.server.downlink.DownlinkRequestCal import java.util.Optional; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.contentToString; + public class RpcReadResponseCallback, T extends ReadResponse> extends RpcLwM2MDownlinkCallback { public RpcReadResponseCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { @@ -32,6 +34,6 @@ public class RpcReadResponseCallback, T extends ReadRe @Override protected Optional serializeSuccessfulResponse(T response) { - return client.contentToString (response.getContent()); + return contentToString(response.getContent()); } } diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java index e73fb54ca7..11f6849410 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/rpc/composite/RpcReadResponseCompositeCallback.java @@ -25,6 +25,8 @@ import org.thingsboard.server.transport.lwm2m.server.rpc.RpcLwM2MDownlinkCallbac import java.util.Optional; +import static org.thingsboard.server.transport.lwm2m.server.LwM2mTransportUtil.contentToString; + public class RpcReadResponseCompositeCallback, T extends ReadCompositeResponse> extends RpcLwM2MDownlinkCallback { public RpcReadResponseCompositeCallback(TransportService transportService, LwM2mClient client, TransportProtos.ToDeviceRpcRequestMsg requestMsg, DownlinkRequestCallback callback) { @@ -33,6 +35,6 @@ public class RpcReadResponseCompositeCallback, T exten @Override protected Optional serializeSuccessfulResponse(T response) { - return client.contentToString (response.getContent()); + return contentToString(response.getContent()); } } From 422d44433ac66c7e868a5562265e34c35835de6d Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 1 Jul 2021 14:03:03 +0300 Subject: [PATCH 198/231] Use hybrid client authentication for DTLS CoAP transport --- .../src/main/resources/thingsboard.yml | 2 - .../coapserver/DefaultCoapServerService.java | 8 +- .../TbCoapDtlsCertificateVerifier.java | 15 +-- .../server/coapserver/TbCoapDtlsSettings.java | 91 ++++--------------- .../transport/coap/CoapTransportResource.java | 20 ++-- .../src/main/resources/tb-coap-transport.yml | 4 +- 6 files changed, 37 insertions(+), 103 deletions(-) diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 2609be7d75..30f7756aca 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -625,8 +625,6 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" - # Secure mode. Allowed values: NO_AUTH, X509 - mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}" # Path to the key store that holds the certificate key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}" # Password used to access the key store 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 3eebbf0d3c..7c869b5039 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 @@ -117,11 +117,9 @@ public class DefaultCoapServerService implements CoapServerService { dtlsCoapEndpointBuilder.setConnector(connector); CoapEndpoint dtlsCoapEndpoint = dtlsCoapEndpointBuilder.build(); server.addEndpoint(dtlsCoapEndpoint); - if (dtlsConnectorConfig.isClientAuthenticationRequired()) { - tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier(); - dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor(); - dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS); - } + tbDtlsCertificateVerifier = (TbCoapDtlsCertificateVerifier) dtlsConnectorConfig.getAdvancedCertificateVerifier(); + dtlsSessionsExecutor = Executors.newSingleThreadScheduledExecutor(); + dtlsSessionsExecutor.scheduleAtFixedRate(this::evictTimeoutSessions, new Random().nextInt((int) getDtlsSessionReportTimeout()), getDtlsSessionReportTimeout(), TimeUnit.MILLISECONDS); } Resource root = server.getRoot(); TbCoapServerMessageDeliverer messageDeliverer = new TbCoapServerMessageDeliverer(root); 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 2076c7a354..92fdef3c84 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 @@ -78,7 +78,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri @Override public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, Boolean clientUsage, boolean truncateCertificatePath, CertificateMessage message, DTLSSession session) { try { - String credentialsBody = null; CertPath certpath = message.getCertificateChain(); X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]); for (X509Certificate cert : chain) { @@ -110,7 +109,6 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri 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) { TransportProtos.SessionInfoProto sessionInfoProto = SessionInfoCreator.create(msg, serviceInfoProvider.getServiceId(), UUID.randomUUID()); @@ -123,15 +121,12 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri CertificateExpiredException | CertificateNotYetValidException e) { log.error(e.getMessage(), e); + AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE, + session.getPeer()); + throw new HandshakeException("Certificate chain could not be validated", alert); } } - if (credentialsBody == null) { - AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.BAD_CERTIFICATE, - session.getPeer()); - throw new HandshakeException("Certificate chain could not be validated", alert); - } else { - return new CertificateVerificationResult(cid, certpath, null); - } + return new CertificateVerificationResult(cid, certpath, null); } catch (HandshakeException e) { log.trace("Certificate validation failed!", e); return new CertificateVerificationResult(cid, e, null); @@ -158,4 +153,4 @@ public class TbCoapDtlsCertificateVerifier implements NewAdvancedCertificateVeri public long getDtlsSessionReportTimeout() { return tbCoapDtlsSessionInMemoryStorage.getDtlsSessionReportTimeout(); } -} \ No newline at end of file +} 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 c25f57d41d..f433909242 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 @@ -15,15 +15,12 @@ */ package org.thingsboard.server.coapserver; -import com.google.common.io.Resources; import lombok.extern.slf4j.Slf4j; import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.CertificateType; -import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier; import org.springframework.beans.factory.annotation.Autowired; 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.Component; import org.thingsboard.server.common.data.ResourceUtils; @@ -35,9 +32,7 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.UnknownHostException; import java.security.GeneralSecurityException; -import java.security.cert.Certificate; import java.util.Collections; -import java.util.Optional; @Slf4j @ConditionalOnProperty(prefix = "transport.coap.dtls", value = "enabled", havingValue = "true", matchIfMissing = false) @@ -50,9 +45,6 @@ public class TbCoapDtlsSettings { @Value("${transport.coap.dtls.bind_port}") private Integer port; - @Value("${transport.coap.dtls.mode:NO_AUTH}") - private String mode; - @Value("${transport.coap.dtls.key_store}") private String keyStoreFile; @@ -81,34 +73,25 @@ public class TbCoapDtlsSettings { private TbServiceInfoProvider serviceInfoProvider; public DtlsConnectorConfig dtlsConnectorConfig() throws UnknownHostException { - Optional securityModeOpt = SecurityMode.parse(mode); - if (securityModeOpt.isEmpty()) { - log.warn("Incorrect configuration of securityMode {}", mode); - throw new RuntimeException("Failed to parse mode property: " + mode + "!"); - } else { - DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(); - configBuilder.setAddress(getInetSocketAddress()); - String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile); - SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath); - SecurityMode securityMode = securityModeOpt.get(); - if (securityMode.equals(SecurityMode.NO_AUTH)) { - configBuilder.setClientAuthenticationRequired(false); - configBuilder.setServerOnly(true); - } else { - configBuilder.setAdvancedCertificateVerifier( - new TbCoapDtlsCertificateVerifier( - transportService, - serviceInfoProvider, - dtlsSessionInactivityTimeout, - dtlsSessionReportTimeout, - skipValidityCheckForClientCert - ) - ); - } - configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), - Collections.singletonList(CertificateType.X_509)); - return configBuilder.build(); - } + DtlsConnectorConfig.Builder configBuilder = new DtlsConnectorConfig.Builder(); + configBuilder.setAddress(getInetSocketAddress()); + String keyStoreFilePath = ResourceUtils.getUri(this, keyStoreFile); + SslContextUtil.Credentials serverCredentials = loadServerCredentials(keyStoreFilePath); + configBuilder.setServerOnly(true); + configBuilder.setClientAuthenticationRequired(false); + configBuilder.setClientAuthenticationWanted(true); + configBuilder.setAdvancedCertificateVerifier( + new TbCoapDtlsCertificateVerifier( + transportService, + serviceInfoProvider, + dtlsSessionInactivityTimeout, + dtlsSessionReportTimeout, + skipValidityCheckForClientCert + ) + ); + configBuilder.setIdentity(serverCredentials.getPrivateKey(), serverCredentials.getCertificateChain(), + Collections.singletonList(CertificateType.X_509)); + return configBuilder.build(); } private SslContextUtil.Credentials loadServerCredentials(String keyStoreFilePath) { @@ -120,43 +103,9 @@ public class TbCoapDtlsSettings { } } - private void loadTrustedCertificates(DtlsConnectorConfig.Builder config, String keyStoreFilePath) { - StaticNewAdvancedCertificateVerifier.Builder trustBuilder = StaticNewAdvancedCertificateVerifier.builder(); - try { - Certificate[] trustedCertificates = SslContextUtil.loadTrustedCertificates( - keyStoreFilePath, keyAlias, - keyStorePassword.toCharArray()); - trustBuilder.setTrustedCertificates(trustedCertificates); - if (trustBuilder.hasTrusts()) { - config.setAdvancedCertificateVerifier(trustBuilder.build()); - } - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException("Failed to load trusted certificates due to: ", e); - } - } - private InetSocketAddress getInetSocketAddress() throws UnknownHostException { InetAddress addr = InetAddress.getByName(host); return new InetSocketAddress(addr, port); } - private enum SecurityMode { - X509, - NO_AUTH; - - static Optional parse(String name) { - SecurityMode mode = null; - if (name != null) { - for (SecurityMode securityMode : SecurityMode.values()) { - if (securityMode.name().equalsIgnoreCase(name)) { - mode = securityMode; - break; - } - } - } - return Optional.ofNullable(mode); - } - - } - -} \ No newline at end of file +} 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 56d49fc779..f5119570c9 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 @@ -223,18 +223,14 @@ public class CoapTransportResource extends AbstractCoapTransportResource { Request request = advanced.getRequest(); String dtlsSessionIdStr = request.getSourceContext().get(DTLS_SESSION_ID_KEY); - if (StringUtils.isNotEmpty(dtlsSessionIdStr)) { - if (dtlsSessionIdMap != null) { - TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap - .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> { - dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis()); - return dtlsSessionInfo; - }); - if (tbCoapDtlsSessionInfo != null) { - processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile()); - } else { - exchange.respond(CoAP.ResponseCode.UNAUTHORIZED); - } + if (dtlsSessionIdMap != null && StringUtils.isNotEmpty(dtlsSessionIdStr)) { + TbCoapDtlsSessionInfo tbCoapDtlsSessionInfo = dtlsSessionIdMap + .computeIfPresent(dtlsSessionIdStr, (dtlsSessionId, dtlsSessionInfo) -> { + dtlsSessionInfo.setLastActivityTime(System.currentTimeMillis()); + return dtlsSessionInfo; + }); + if (tbCoapDtlsSessionInfo != null) { + processRequest(exchange, type, request, tbCoapDtlsSessionInfo.getSessionInfoProto(), tbCoapDtlsSessionInfo.getDeviceProfile()); } else { processAccessTokenRequest(exchange, type, request); } diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index ee3445c90d..1033301b5b 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -96,8 +96,6 @@ transport: bind_address: "${COAP_DTLS_BIND_ADDRESS:0.0.0.0}" # CoAP DTLS bind port bind_port: "${COAP_DTLS_BIND_PORT:5684}" - # Secure mode. Allowed values: NO_AUTH, X509 - mode: "${COAP_DTLS_SECURE_MODE:NO_AUTH}" # Path to the key store that holds the certificate key_store: "${COAP_DTLS_KEY_STORE:coapserver.jks}" # Password used to access the key store @@ -299,4 +297,4 @@ management: web: exposure: # Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics). - include: '${METRICS_ENDPOINTS_EXPOSE:info}' \ No newline at end of file + include: '${METRICS_ENDPOINTS_EXPOSE:info}' From 0a94b7d8935df477ecb65180df17d7684431dd16 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Thu, 1 Jul 2021 17:04:57 +0300 Subject: [PATCH 199/231] Correct data format for audit log --- 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 79e8d9e1d3..534c9d6c24 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -518,7 +518,7 @@ audit-log: 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}" + 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}" From ee55c0bf85a594161ce3aa7bfbd3d2adc8d06929 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 1 Jul 2021 17:08:41 +0300 Subject: [PATCH 200/231] Improvements to key store read for lwm2m. --- .../LwM2MServerSecurityInfoRepository.java | 52 ++----- .../src/main/resources/thingsboard.yml | 14 +- .../resources/application-test.properties | 5 +- .../LwM2MTransportBootstrapService.java | 146 ++---------------- .../lwm2m/config/LwM2MSecureServerConfig.java | 8 +- .../config/LwM2MTransportBootstrapConfig.java | 16 +- .../config/LwM2MTransportServerConfig.java | 16 +- .../server/DefaultLwM2mTransportService.java | 117 +------------- .../src/main/resources/tb-lwm2m-transport.yml | 14 +- 9 files changed, 51 insertions(+), 337 deletions(-) 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 78f849667f..b4ac3d30fd 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 @@ -30,8 +30,12 @@ import java.math.BigInteger; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyFactory; +import java.security.KeyStore; import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.PublicKey; +import java.security.UnrecoverableKeyException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.spec.ECGenParameterSpec; @@ -62,49 +66,23 @@ public class LwM2MServerSecurityInfoRepository { bsServ.setPort(serverConfig.getPort()); bsServ.setSecurityHost(serverConfig.getSecureHost()); bsServ.setSecurityPort(serverConfig.getSecurePort()); - bsServ.setServerPublicKey(getPublicKey(serverConfig.getCertificateAlias(), this.serverConfig.getPublicX(), this.serverConfig.getPublicY())); + bsServ.setServerPublicKey(getPublicKey(serverConfig)); return bsServ; } - private String getPublicKey(String alias, String publicServerX, String publicServerY) { - String publicKey = getServerPublicKeyX509(alias); - return publicKey != null ? publicKey : getRPKPublicKey(publicServerX, publicServerY); - } - - private String getServerPublicKeyX509(String alias) { - try { - X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(alias); - return Hex.encodeHexString(serverCertificate.getEncoded()); - } catch (CertificateEncodingException | KeyStoreException e) { - e.printStackTrace(); - } - return null; - } - - private String getRPKPublicKey(String publicServerX, String publicServerY) { + private String getPublicKey(LwM2MSecureServerConfig config) { try { - /** Get Elliptic Curve Parameter spec for secp256r1 */ - AlgorithmParameters algoParameters = AlgorithmParameters.getInstance("EC"); - algoParameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec parameterSpec = algoParameters.getParameterSpec(ECParameterSpec.class); - if (publicServerX != null && !publicServerX.isEmpty() && publicServerY != null && !publicServerY.isEmpty()) { - /** Get point values */ - byte[] publicX = Hex.decodeHex(publicServerX.toCharArray()); - byte[] publicY = Hex.decodeHex(publicServerY.toCharArray()); - /** Create key specs */ - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - /** Get keys */ - PublicKey publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - if (publicKey != null && publicKey.getEncoded().length > 0) { - return Hex.encodeHexString(publicKey.getEncoded()); - } + KeyStore keyStore = serverConfig.getKeyStoreValue(); + if (keyStore != null) { + X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(config.getCertificateAlias()); + return Hex.encodeHexString(serverCertificate.getPublicKey().getEncoded()); } - } catch (GeneralSecurityException | IllegalArgumentException e) { - log.error("[{}] Failed generate Server RPK for profile", e.getMessage()); - throw new RuntimeException(e); + } catch (Exception e) { + log.trace("Failed to fetch public key from key store!", e); + } - return null; + return ""; } + } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 30f7756aca..3b83c3d3b6 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -649,12 +649,9 @@ transport: 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_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}" - public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" - private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" # Only Certificate_x509: - alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" + alias: "${LWM2M_KEYSTORE_SERVER_ALIAS:server}" + password: "${LWM2M_KEYSTORE_SERVER_PASSWORD:server_ks_password}" skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: enable: "${LWM2M_ENABLED_BS:true}" @@ -664,12 +661,9 @@ transport: security: bind_address: "${LWM2M_BIND_ADDRESS_SECURITY_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 - 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}" + alias: "${LWM2M_KEYSTORE_BS_ALIAS:bootstrap}" + password: "${LWM2M_KEYSTORE_BS_PASSWORD:server_ks_password}" security: # Certificate_x509: # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index 02d43723ed..d82de51785 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,4 +1,7 @@ transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks transport.lwm2m.security.key_store_password=server edges.enabled=true -transport.lwm2m.bootstrap.security.alias=server \ No newline at end of file +transport.lwm2m.server.security.alias=server +transport.lwm2m.server.security.password=server +transport.lwm2m.bootstrap.security.alias=server +transport.lwm2m.bootstrap.security.password=server \ No newline at end of file diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 8823881ae7..015cf1cbcf 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -31,6 +31,7 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBoots import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig; import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; +import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -65,10 +66,7 @@ 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 -//TODO: @ybondarenko please refactor this to be similar to DefaultLwM2mTransportService public class LwM2MTransportBootstrapService { - private PublicKey publicKey; - private PrivateKey privateKey; private boolean pskMode = false; private final LwM2MTransportServerConfig serverConfig; @@ -101,44 +99,34 @@ public class LwM2MTransportBootstrapService { builder.setLocalAddress(bootstrapConfig.getHost(), bootstrapConfig.getPort()); builder.setLocalSecureAddress(bootstrapConfig.getSecureHost(), bootstrapConfig.getSecurePort()); - /** Create CoAP Config */ + /* Create CoAP Config */ builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); - /** Define model provider (Create Models )*/ + /* Define model provider (Create Models )*/ - /** Create credentials */ + /* Create credentials */ this.setServerWithCredentials(builder); // /** Set securityStore with new ConfigStore */ // builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore); - /** SecurityStore */ + /* SecurityStore */ builder.setSecurityStore(lwM2MBootstrapSecurityStore); - /** Create and Set DTLS Config */ + /* Create and Set DTLS Config */ DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); dtlsConfig.setRecommendedSupportedGroupsOnly(serverConfig.isRecommendedSupportedGroups()); dtlsConfig.setRecommendedCipherSuitesOnly(serverConfig.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); - } + dtlsConfig.setSupportedCipherSuites(this.pskMode ? DefaultLwM2mTransportService.PSK_CIPHER_SUITES : DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES); - /** Set DTLS Config */ + /* Set DTLS Config */ builder.setDtlsConfig(dtlsConfig); BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore); builder.setSessionManager(sessionManager); - /** Create BootstrapServer */ + /* Create BootstrapServer */ return builder.build(); } @@ -153,19 +141,15 @@ public class LwM2MTransportBootstrapService { trustedCertificates[0] = rootCAX509Cert; builder.setTrustedCertificates(trustedCertificates); } else { - /** by default trust all */ + /* by default trust all */ builder.setTrustedCertificates(new X509Certificate[0]); } } - } else if (this.setServerRPK(builder)) { - this.infoPramsUri("RPK"); - this.infoParamsBootstrapServerKey(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 BootStrapServer"); this.pskMode = true; - this.infoPramsUri("PSK"); } } catch (KeyStoreException ex) { log.error("[{}] Unable to load X509 files server", ex.getMessage()); @@ -173,19 +157,14 @@ public class LwM2MTransportBootstrapService { } private boolean setBuilderX509(LeshanBootstrapServerBuilder 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) serverConfig.getKeyStoreValue().getCertificate(this.bootstrapConfig.getCertificateAlias()); - PrivateKey privateKey = (PrivateKey) serverConfig.getKeyStoreValue().getKey(this.bootstrapConfig.getCertificateAlias(), serverConfig.getKeyStorePassword() == null ? null : serverConfig.getKeyStorePassword().toCharArray()); + PrivateKey privateKey = (PrivateKey) serverConfig.getKeyStoreValue().getKey(this.bootstrapConfig.getCertificateAlias(), serverConfig.getCertificatePassword() == null ? null : serverConfig.getCertificatePassword().toCharArray()); PublicKey publicKey = serverCertificate.getPublicKey(); 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}); - this.infoParamsServerX509(serverCertificate, publicKey, privateKey); return true; } else { return false; @@ -196,105 +175,4 @@ public class LwM2MTransportBootstrapService { } } - private void infoParamsServerX509(X509Certificate certificate, PublicKey publicKey, PrivateKey privateKey) { - try { - this.infoPramsUri("X509"); - log.info("\n- X509 Certificate (Hex): [{}]", - Hex.encodeHexString(certificate.getEncoded())); - this.infoParamsBootstrapServerKey(publicKey, privateKey); - } catch (CertificateEncodingException e) { - log.error("", e); - } - } - - private void infoPramsUri(String mode) { - log.info("Bootstrap Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]", - mode, - this.bootstrapConfig.getHost(), - this.bootstrapConfig.getPort(), - this.bootstrapConfig.getSecureHost(), - this.bootstrapConfig.getSecurePort()); - } - - - private boolean setServerRPK(LeshanBootstrapServerBuilder builder) { - try { - this.generateKeyForBootstrapRPK(); - if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && - this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPublicKey(this.publicKey); -// builder.setCertificateChain(new X509Certificate[] { serverCertificate }); - /// Trust all certificates. - builder.setTrustedCertificates(new X509Certificate[0]); - builder.setPrivateKey(this.privateKey); - return true; - } - } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) { - log.error("Fail create Bootstrap Server with RPK", e); - } - return false; - } - - - /** - * From yml: bootstrap - * public_x: "${LWM2M_SERVER_PUBLIC_X_BS:993ef2b698c6a9c0c1d8be78b13a9383c0854c7c7c7a504d289b403794648183}" - * public_y: "${LWM2M_SERVER_PUBLIC_Y_BS:267412d5fc4e5ceb2257cb7fd7f76ebdac2fa9aa100afb162e990074cc0bfaa2}" - * private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED_BS:9dbdbb073fc63570693a9aaf1013414e261c571f27e27fc6a8c1c2ad9347875a}" - */ - private void generateKeyForBootstrapRPK() 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); - LwM2MTransportBootstrapConfig serverConfig = this.bootstrapConfig; - if (StringUtils.isNotEmpty(serverConfig.getPublicX()) && StringUtils.isNotEmpty(serverConfig.getPublicY())) { - /** Get point values */ - 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); - } - String privateEncodedKey = serverConfig.getPrivateEncoded(); - if (StringUtils.isNotEmpty(privateEncodedKey)) { - /** Get private key */ - 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", privateEncodedKey); - } - } - } - - private void infoParamsBootstrapServerKey(PublicKey publicKey, PrivateKey privateKey) { - /** Get x coordinate */ - byte[] x = ((ECPublicKey) publicKey).getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /** Get Y coordinate */ - byte[] y = ((ECPublicKey) publicKey).getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /** Get Curves params */ - String params = ((ECPublicKey) publicKey).getParams().toString(); - String privHex = Hex.encodeHexString(privateKey.getEncoded()); - log.info("\n- Public Key (Hex): [{}] \n" + - "- Private Key (Hex): [{}], \n" + - "public_x: \"${LWM2M_SERVER_PUBLIC_X_BS:{}}\" \n" + - "public_y: \"${LWM2M_SERVER_PUBLIC_Y_BS:{}}\" \n" + - "private_encoded: \"${LWM2M_SERVER_PRIVATE_ENCODED_BS:{}}\" \n" + - "- Elliptic Curve parameters : [{}]", - Hex.encodeHexString(publicKey.getEncoded()), - privHex, - Hex.encodeHexString(x), - Hex.encodeHexString(y), - privHex, - params); - } } 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 index fcca9fb975..a525c5f157 100644 --- 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 @@ -27,12 +27,8 @@ public interface LwM2MSecureServerConfig { Integer getSecurePort(); - String getPublicX(); - - String getPublicY(); - - String getPrivateEncoded(); - String getCertificateAlias(); + String getCertificatePassword(); + } 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 c0379f9cee..6778056dd0 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 @@ -46,20 +46,12 @@ public class LwM2MTransportBootstrapConfig implements LwM2MSecureServerConfig { @Value("${transport.lwm2m.bootstrap.security.bind_port:}") private Integer securePort; - @Getter - @Value("${transport.lwm2m.bootstrap.security.public_x:}") - private String publicX; - - @Getter - @Value("${transport.lwm2m.bootstrap.security.public_y:}") - private String publicY; - - @Getter - @Value("${transport.lwm2m.bootstrap.security.private_encoded:}") - private String privateEncoded; - @Getter @Value("${transport.lwm2m.bootstrap.security.alias:}") private String certificateAlias; + @Getter + @Value("${transport.lwm2m.bootstrap.security.password:}") + private String certificatePassword; + } 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 295b1eb9ba..4f9f0798c2 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 @@ -120,22 +120,14 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @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; + @Getter + @Value("${transport.lwm2m.server.security.password:}") + private String certificatePassword; + @Getter @Value("${transport.lwm2m.log_max_length:}") private int logMaxLength; 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 8059d210dc..bf32b2f817 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 @@ -21,14 +21,12 @@ 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; 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.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; import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; @@ -41,24 +39,9 @@ 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; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.InvalidParameterSpecException; -import java.security.spec.KeySpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Arrays; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8; @@ -75,8 +58,6 @@ 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 final LwM2mTransportContext context; private final LwM2MTransportServerConfig config; @@ -97,14 +78,13 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { new LWM2MGenerationPSkRPkECC(); } this.server = getLhServer(); - /** + /* * Add a resource to the server. * CoapResource -> * path = FW_PACKAGE or SW_PACKAGE * nameFile = "BC68JAR01A09_TO_BC68JAR01A10.bin" * "coap://host:port/{path}/{token}/{nameFile}" */ - LwM2mTransportCoapResource otaCoapResource = new LwM2mTransportCoapResource(otaPackageDataCache, FIRMWARE_UPDATE_COAP_RESOURCE); this.server.coap().getServer().add(otaCoapResource); this.startLhServer(); @@ -170,29 +150,23 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { 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"); } } private boolean setBuilderX509(LeshanServerBuilder builder) { try { X509Certificate serverCertificate = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getCertificateAlias()); - PrivateKey privateKey = (PrivateKey) config.getKeyStoreValue().getKey(config.getCertificateAlias(), config.getKeyStorePassword() == null ? null : config.getKeyStorePassword().toCharArray()); + PrivateKey privateKey = (PrivateKey) config.getKeyStoreValue().getKey(config.getCertificateAlias(), config.getCertificatePassword() == null ? null : config.getCertificatePassword().toCharArray()); PublicKey publicKey = serverCertificate.getPublicKey(); 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}); - this.infoParamsServerX509(serverCertificate, publicKey, privateKey); return true; } else { return false; @@ -203,93 +177,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { } } - private void infoParamsServerX509(X509Certificate certificate, PublicKey publicKey, PrivateKey privateKey) { - try { - infoPramsUri("X509"); - log.info("\n- X509 Certificate (Hex): [{}]", - Hex.encodeHexString(certificate.getEncoded())); - this.infoParamsServerKey(publicKey, privateKey); - } catch (CertificateEncodingException e) { - log.error("", e); - } - } - - private void infoPramsUri(String mode) { - LwM2MTransportServerConfig lwM2MTransportServerConfig = config; - log.info("Server uses [{}]: serverNoSecureURI : [{}:{}], serverSecureURI : [{}:{}]", mode, - lwM2MTransportServerConfig.getHost(), - lwM2MTransportServerConfig.getPort(), - lwM2MTransportServerConfig.getSecureHost(), - lwM2MTransportServerConfig.getSecurePort()); - } - - private boolean setServerRPK(LeshanServerBuilder builder) { - try { - this.loadOrGenerateRPKKeys(); - if (this.publicKey != null && this.publicKey.getEncoded().length > 0 && - this.privateKey != null && this.privateKey.getEncoded().length > 0) { - builder.setPublicKey(this.publicKey); - builder.setPrivateKey(this.privateKey); - return true; - } - } catch (NoSuchAlgorithmException | InvalidParameterSpecException | InvalidKeySpecException e) { - log.error("Fail create Server with RPK", e); - } - return false; - } - - private void loadOrGenerateRPKKeys() 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); - 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()); - KeySpec publicKeySpec = new ECPublicKeySpec(new ECPoint(new BigInteger(publicX), new BigInteger(publicY)), - parameterSpec); - this.publicKey = KeyFactory.getInstance("EC").generatePublic(publicKeySpec); - } - 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", privateEncodedKey); - } - } - } - - private void infoParamsServerKey(PublicKey publicKey, PrivateKey privateKey) { - /* Get x coordinate */ - byte[] x = ((ECPublicKey) publicKey).getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /* Get Y coordinate */ - byte[] y = ((ECPublicKey) publicKey).getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /* Get Curves params */ - String params = ((ECPublicKey) publicKey).getParams().toString(); - String privHex = Hex.encodeHexString(privateKey.getEncoded()); - log.info(" \n- Public Key (Hex): [{}] \n" + - "- Private Key (Hex): [{}], \n" + - "public_x: \"${LWM2M_SERVER_PUBLIC_X:{}}\" \n" + - "public_y: \"${LWM2M_SERVER_PUBLIC_Y:{}}\" \n" + - "private_encoded: \"${LWM2M_SERVER_PRIVATE_ENCODED:{}}\" \n" + - "- Elliptic Curve parameters : [{}]", - Hex.encodeHexString(publicKey.getEncoded()), - privHex, - Hex.encodeHexString(x), - Hex.encodeHexString(y), - privHex, - params); - } - @Override public String getName() { return "LWM2M"; diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index d7e97231e0..695053d417 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -108,12 +108,9 @@ transport: 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_x: "${LWM2M_SERVER_PUBLIC_X:05064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f358}" - public_y: "${LWM2M_SERVER_PUBLIC_Y:5eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" - private_encoded: "${LWM2M_SERVER_PRIVATE_ENCODED:308193020100301306072a8648ce3d020106082a8648ce3d030107047930770201010420dc774b309e547ceb48fee547e104ce201a9c48c449dc5414cd04e7f5cf05f67ba00a06082a8648ce3d030107a1440342000405064b9e6762dd8d8b8a52355d7b4d8b9a3d64e6d2ee277d76c248861353f3585eeb1838e4f9e37b31fa347aef5ce3431eb54e0a2506910c5e0298817445721b}" # Only Certificate_x509: - alias: "${LWM2M_KEYSTORE_ALIAS_SERVER:server}" + alias: "${LWM2M_KEYSTORE_SERVER_ALIAS:server}" + password: "${LWM2M_KEYSTORE_SERVER_PASSWORD:server_ks_password}" skip_validity_check_for_client_cert: "${TB_LWM2M_SERVER_SECURITY_SKIP_VALIDITY_CHECK_FOR_CLIENT_CERT:false}" bootstrap: enable: "${LWM2M_ENABLED_BS:true}" @@ -123,12 +120,9 @@ transport: security: bind_address: "${LWM2M_BIND_ADDRESS_SECURITY_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 - 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}" + alias: "${LWM2M_KEYSTORE_BS_ALIAS:bootstrap}" + password: "${LWM2M_KEYSTORE_BS_PASSWORD:server_ks_password}" security: # Certificate_x509: # To get helps about files format and how to generate it, see: https://github.com/eclipse/leshan/wiki/Credential-files-format From 690c97dd4d66e88af88b9946387f25bc2b91e282 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 1 Jul 2021 17:32:36 +0300 Subject: [PATCH 201/231] Renaming of the lwm2m configuration properties --- .../src/main/resources/thingsboard.yml | 34 +++--- .../resources/application-test.properties | 8 +- .../LwM2MTransportBootstrapService.java | 27 +---- .../config/LwM2MTransportBootstrapConfig.java | 4 +- .../config/LwM2MTransportServerConfig.java | 12 +- .../secure/LWM2MGenerationPSkRPkECC.java | 110 ------------------ .../server/DefaultLwM2mTransportService.java | 4 - .../src/main/resources/tb-lwm2m-transport.yml | 33 +++--- 8 files changed, 43 insertions(+), 189 deletions(-) delete mode 100644 common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 3b83c3d3b6..65601aa174 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -647,42 +647,40 @@ transport: bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" bind_port: "${LWM2M_BIND_PORT:5685}" security: - bind_address: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_SECURITY:5686}" + bind_address: "${LWM2M_SECURITY_BIND_ADDRESS:0.0.0.0}" + bind_port: "${LWM2M_SECURITY_BIND_PORT:5686}" # Only Certificate_x509: - alias: "${LWM2M_KEYSTORE_SERVER_ALIAS:server}" - password: "${LWM2M_KEYSTORE_SERVER_PASSWORD:server_ks_password}" + key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}" + key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}" 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}" - bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_BS:5687}" + bind_address: "${LWM2M_BS_BIND_ADDRESS:0.0.0.0}" + bind_port: "${LWM2M_BS_BIND_PORT:5687}" security: - bind_address: "${LWM2M_BIND_ADDRESS_SECURITY_BS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" + bind_address: "${LWM2M_BS_SECURITY_BIND_ADDRESS:0.0.0.0}" + bind_port: "${LWM2M_BS_SECURITY_BIND_PORT:5688}" # Only Certificate_x509: - alias: "${LWM2M_KEYSTORE_BS_ALIAS:bootstrap}" - password: "${LWM2M_KEYSTORE_BS_PASSWORD:server_ks_password}" + key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}" + key_password: "${LWM2M_BS_KEY_PASSWORD:server_ks_password}" 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}" + key_store: "${LWM2M_KEYSTORE:lwm2mserver.jks}" + key_store_password: "${LWM2M_KEYSTORE_PASSWORD:server_ks_password}" + root_alias: "${LWM2M_SERVER_ROOT_CA_ALIAS:rootca}" + recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" + recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" timeout: "${LWM2M_TIMEOUT:120000}" - recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" - recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" 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}" - log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}" + log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}" # Use redis for Security and Registration stores redis.enabled: "${LWM2M_REDIS_ENABLED:false}" snmp: diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index d82de51785..6e6f33ac46 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -1,7 +1,7 @@ transport.lwm2m.security.key_store=lwm2m/credentials/serverKeyStore.jks transport.lwm2m.security.key_store_password=server edges.enabled=true -transport.lwm2m.server.security.alias=server -transport.lwm2m.server.security.password=server -transport.lwm2m.bootstrap.security.alias=server -transport.lwm2m.bootstrap.security.password=server \ No newline at end of file +transport.lwm2m.server.security.key_alias=server +transport.lwm2m.server.security.key_password=server +transport.lwm2m.bootstrap.security.key_alias=server +transport.lwm2m.bootstrap.security.key_password=server \ No newline at end of file diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 015cf1cbcf..6224be059d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -18,48 +18,26 @@ 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.boot.autoconfigure.condition.ConditionalOnExpression; 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.config.LwM2MTransportServerConfig; -import org.thingsboard.server.transport.lwm2m.secure.LWM2MGenerationPSkRPkECC; import org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.math.BigInteger; -import java.security.AlgorithmParameters; -import java.security.KeyFactory; import java.security.KeyStore; import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPublicKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.InvalidParameterSpecException; -import java.security.spec.KeySpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.Arrays; - -import static org.eclipse.californium.scandium.dtls.cipher.CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; -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.LwM2mNetworkConfig.getCoapConfig; @Slf4j @@ -78,9 +56,6 @@ public class LwM2MTransportBootstrapService { @PostConstruct public void init() { - if (serverConfig.getEnableGenNewKeyPskRpk()) { - new LWM2MGenerationPSkRPkECC(); - } log.info("Starting LwM2M transport bootstrap server..."); this.server = getLhBootstrapServer(); this.server.start(); 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 6778056dd0..0806ef9e90 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 @@ -47,11 +47,11 @@ public class LwM2MTransportBootstrapConfig implements LwM2MSecureServerConfig { private Integer securePort; @Getter - @Value("${transport.lwm2m.bootstrap.security.alias:}") + @Value("${transport.lwm2m.bootstrap.security.key_alias:}") private String certificateAlias; @Getter - @Value("${transport.lwm2m.bootstrap.security.password:}") + @Value("${transport.lwm2m.bootstrap.security.key_password:}") private String certificatePassword; } 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 4f9f0798c2..8aeacc72c2 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 @@ -53,11 +53,11 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { private long sessionReportTimeout; @Getter - @Value("${transport.lwm2m.recommended_ciphers:}") + @Value("${transport.lwm2m.security.recommended_ciphers:}") private boolean recommendedCiphers; @Getter - @Value("${transport.lwm2m.recommended_supported_groups:}") + @Value("${transport.lwm2m.security.recommended_supported_groups:}") private boolean recommendedSupportedGroups; @Getter @@ -96,10 +96,6 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { @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; @@ -121,11 +117,11 @@ public class LwM2MTransportServerConfig implements LwM2MSecureServerConfig { private Integer securePort; @Getter - @Value("${transport.lwm2m.server.security.alias:}") + @Value("${transport.lwm2m.server.security.key_alias:}") private String certificateAlias; @Getter - @Value("${transport.lwm2m.server.security.password:}") + @Value("${transport.lwm2m.server.security.key_password:}") private String certificatePassword; @Getter 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 deleted file mode 100644 index 22c5878a58..0000000000 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LWM2MGenerationPSkRPkECC.java +++ /dev/null @@ -1,110 +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; - -import lombok.extern.slf4j.Slf4j; -import org.eclipse.leshan.core.util.Hex; - -import java.security.InvalidAlgorithmParameterException; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -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; - -@Slf4j -public class LWM2MGenerationPSkRPkECC { - - public LWM2MGenerationPSkRPkECC() { - generationPSkKey(); - generationRPKECCKey(); - } - - private void generationPSkKey() { - /* 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] - */ - SecureRandom randomPSK = new SecureRandom(); - 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 */ - 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)] - */ - KeyPairGenerator kpg = null; - try { - kpg = KeyPairGenerator.getInstance(algorithm, provider); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { - log.error("", e); - } - ECGenParameterSpec ecsp = new ECGenParameterSpec(nameParameterSpec); - try { - kpg.initialize(ecsp); - } catch (InvalidAlgorithmParameterException e) { - log.error("", e); - } - - KeyPair kp = kpg.genKeyPair(); - PrivateKey privKey = kp.getPrivate(); - PublicKey pubKey = kp.getPublic(); - - if (pubKey instanceof ECPublicKey) { - ECPublicKey ecPublicKey = (ECPublicKey) pubKey; - /* Get x coordinate */ - byte[] x = ecPublicKey.getW().getAffineX().toByteArray(); - if (x[0] == 0) - x = Arrays.copyOfRange(x, 1, x.length); - - /* Get Y coordinate */ - byte[] y = ecPublicKey.getW().getAffineY().toByteArray(); - if (y[0] == 0) - y = Arrays.copyOfRange(y, 1, y.length); - - /* 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", - Hex.encodeHexString(pubKey.getEncoded()), - privHex, - Hex.encodeHexString(x), - Hex.encodeHexString(y), - privHex, - ecPublicKey.getParams().toString()); - } - } -} - 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 bf32b2f817..a2cc1c0b83 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 @@ -29,7 +29,6 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.cache.ota.OtaPackageDataCache; 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; @@ -74,9 +73,6 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { @PostConstruct public void init() { - if (config.getEnableGenNewKeyPskRpk()) { - new LWM2MGenerationPSkRPkECC(); - } this.server = getLhServer(); /* * Add a resource to the server. diff --git a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml index 695053d417..b6f29ea9d7 100644 --- a/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml +++ b/transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml @@ -106,41 +106,40 @@ transport: bind_address: "${LWM2M_BIND_ADDRESS:0.0.0.0}" bind_port: "${LWM2M_BIND_PORT:5685}" security: - bind_address: "${LWM2M_BIND_ADDRESS_SECURITY:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_SECURITY:5686}" + bind_address: "${LWM2M_SECURITY_BIND_ADDRESS:0.0.0.0}" + bind_port: "${LWM2M_SECURITY_BIND_PORT:5686}" # Only Certificate_x509: - alias: "${LWM2M_KEYSTORE_SERVER_ALIAS:server}" - password: "${LWM2M_KEYSTORE_SERVER_PASSWORD:server_ks_password}" + key_alias: "${LWM2M_SERVER_KEY_ALIAS:server}" + key_password: "${LWM2M_SERVER_KEY_PASSWORD:server_ks_password}" 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}" - bind_address: "${LWM2M_BIND_ADDRESS_BS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_BS:5687}" + bind_address: "${LWM2M_BS_BIND_ADDRESS:0.0.0.0}" + bind_port: "${LWM2M_BS_BIND_PORT:5687}" security: - bind_address: "${LWM2M_BIND_ADDRESS_SECURITY_BS:0.0.0.0}" - bind_port: "${LWM2M_BIND_PORT_SECURITY_BS:5688}" + bind_address: "${LWM2M_BS_SECURITY_BIND_ADDRESS:0.0.0.0}" + bind_port: "${LWM2M_BS_SECURITY_BIND_PORT:5688}" # Only Certificate_x509: - alias: "${LWM2M_KEYSTORE_BS_ALIAS:bootstrap}" - password: "${LWM2M_KEYSTORE_BS_PASSWORD:server_ks_password}" + key_alias: "${LWM2M_BS_KEY_ALIAS:bootstrap}" + key_password: "${LWM2M_BS_KEY_PASSWORD:server_ks_password}" 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}" + key_store: "${LWM2M_KEYSTORE:lwm2mserver.jks}" + key_store_password: "${LWM2M_KEYSTORE_PASSWORD:server_ks_password}" + root_alias: "${LWM2M_SERVER_ROOT_CA_ALIAS:rootca}" + recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" + recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" timeout: "${LWM2M_TIMEOUT:120000}" - recommended_ciphers: "${LWM2M_RECOMMENDED_CIPHERS:false}" - recommended_supported_groups: "${LWM2M_RECOMMENDED_SUPPORTED_GROUPS:true}" 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}" - log_max_length: "${LWM2M_LOG_MAX_LENGTH:100}" + log_max_length: "${LWM2M_LOG_MAX_LENGTH:1024}" # Use redis for Security and Registration stores redis.enabled: "${LWM2M_REDIS_ENABLED:false}" From 38e3dcc9d717a8f2e9bc4efee158451849556659 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Thu, 1 Jul 2021 19:58:12 +0300 Subject: [PATCH 202/231] Use certificate chain from keystore for server identity --- .../lwm2m/bootstrap/LwM2MTransportBootstrapService.java | 6 ++++-- .../lwm2m/server/DefaultLwM2mTransportService.java | 7 +++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java index 8823881ae7..4411bb1c9d 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.bootstrap; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.leshan.core.util.Hex; import org.eclipse.leshan.server.bootstrap.BootstrapSessionManager; @@ -178,13 +179,14 @@ public class LwM2MTransportBootstrapService { * For idea => KeyStorePathResource == common/transport/lwm2m/src/main/resources/credentials: in LwM2MTransportContextServer: credentials/serverKeyStore.jks */ try { - X509Certificate serverCertificate = (X509Certificate) serverConfig.getKeyStoreValue().getCertificate(this.bootstrapConfig.getCertificateAlias()); + X509Certificate[] certificateChain = SslContextUtil.asX509Certificates(serverConfig.getKeyStoreValue().getCertificateChain(this.bootstrapConfig.getCertificateAlias())); + X509Certificate serverCertificate = certificateChain[0]; 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()); builder.setPrivateKey(privateKey); - builder.setCertificateChain(new X509Certificate[]{serverCertificate}); + builder.setCertificateChain(certificateChain); this.infoParamsServerX509(serverCertificate, publicKey, privateKey); return true; } else { 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 8059d210dc..8308569d29 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,6 +17,7 @@ package org.thingsboard.server.transport.lwm2m.server; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.elements.util.SslContextUtil; import org.eclipse.californium.scandium.config.DtlsConnectorConfig; import org.eclipse.californium.scandium.dtls.cipher.CipherSuite; import org.eclipse.leshan.core.node.codec.DefaultLwM2mNodeDecoder; @@ -47,6 +48,7 @@ import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.security.interfaces.ECPublicKey; @@ -185,13 +187,14 @@ public class DefaultLwM2mTransportService implements LwM2MTransportService { private boolean setBuilderX509(LeshanServerBuilder builder) { try { - X509Certificate serverCertificate = (X509Certificate) config.getKeyStoreValue().getCertificate(config.getCertificateAlias()); + X509Certificate[] certificateChain = SslContextUtil.asX509Certificates(config.getKeyStoreValue().getCertificateChain(config.getCertificateAlias())); + X509Certificate serverCertificate = certificateChain[0]; 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()); builder.setPrivateKey(privateKey); - builder.setCertificateChain(new X509Certificate[]{serverCertificate}); + builder.setCertificateChain(certificateChain); this.infoParamsServerX509(serverCertificate, publicKey, privateKey); return true; } else { From bbc4dd7dd8056ac92c12d5d5be3df83699f5ec55 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 1 Jul 2021 17:41:41 +0300 Subject: [PATCH 203/231] UI: Fixed work 'widget filter type' after migrating dashboard - change detection OnPush --- .../components/dashboard-page/dashboard-page.component.ts | 3 ++- .../dashboard-page/dashboard-widget-select.component.ts | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) 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 5f3478bccc..e77b6a461e 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 @@ -1298,6 +1298,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC types: widgetTypesList, typesUpdated: (newTypes) => { this.filterWidgetTypes = newTypes.filter(type => type.display).map(type => type.type); + this.cd.markForCheck(); } } as DisplayWidgetTypesPanelData }, @@ -1308,7 +1309,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC ]; const injector = Injector.create({parent: this.viewContainerRef.injector, providers}); overlayRef.attach(new ComponentPortal(DisplayWidgetTypesPanelComponent, this.viewContainerRef, injector)); - this.cd.detectChanges(); + this.cd.markForCheck(); } onCloseSearchBundle() { diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts index 645d955deb..bcc239eab0 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { IAliasController } from '@core/api/widget-api.models'; import { NULL_UUID } from '@shared/models/id/has-uuid'; @@ -88,7 +88,8 @@ export class DashboardWidgetSelectComponent implements OnInit { widgetsBundleSelected: EventEmitter = new EventEmitter(); constructor(private widgetsService: WidgetService, - private sanitizer: DomSanitizer) { + private sanitizer: DomSanitizer, + private cd: ChangeDetectorRef) { this.widgetsBundles$ = this.search$.asObservable().pipe( distinctUntilChanged(), switchMap(search => this.fetchWidgetBundle(search)) @@ -128,6 +129,7 @@ export class DashboardWidgetSelectComponent implements OnInit { ); setTimeout(() => { this.widgetTypes = widgetTypes; + this.cd.markForCheck(); }); return widgetInfos; }), From e38212808153eca4eb6169daf01ed690a2aade2c Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 2 Jul 2021 12:17:41 +0300 Subject: [PATCH 204/231] UI: LwmM2M device provife change style ID parameters --- .../profile/device/lwm2m/lwm2m-object-list.component.html | 2 +- .../lwm2m-observe-attr-telemetry-instances.component.html | 4 ++-- .../lwm2m-observe-attr-telemetry-instances.component.scss | 6 +++++- .../lwm2m-observe-attr-telemetry-instances.component.ts | 2 +- .../lwm2m-observe-attr-telemetry-resources.component.html | 2 +- .../lwm2m-observe-attr-telemetry-resources.component.scss | 4 ++++ .../lwm2m-observe-attr-telemetry-resources.component.ts | 4 +++- .../lwm2m/lwm2m-observe-attr-telemetry.component.html | 2 +- .../lwm2m/lwm2m-observe-attr-telemetry.component.scss | 5 ++--- .../device/lwm2m/lwm2m-observe-attr-telemetry.component.ts | 2 +- ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 11 files changed, 22 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html index 1c8de300d3..369d6fa180 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-object-list.component.html @@ -22,7 +22,7 @@ [selectable]="!disabled" [removable]="!disabled" (removed)="remove(objectLwm2m)"> - {{objectLwm2m.name}} <{{objectLwm2m.keyId}}> + {{objectLwm2m.name}} #{{objectLwm2m.keyId}} close - +
- {{ 'device-profile.lwm2m.instance' | translate }} <{{instances.get('id').value}}> + {{ 'device-profile.lwm2m.instance' | translate }} #{{instances.get('id').value}}
`; + return `${this.translate.instant('device-profile.lwm2m.instance')} #${instance.id}`; } disableObserveInstance = (instance: AbstractControl): boolean => { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.html b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.html index ecb8214f52..8d9030d89c 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.html @@ -38,7 +38,7 @@ *ngFor="let resourceLwM2M of resourcesFormArray.controls; let $index = index; trackBy: trackByParams">
- <{{resourceLwM2M.get('id').value}}> {{resourceLwM2M.get('name').value}} + #{{resourceLwM2M.get('id').value}} {{resourceLwM2M.get('name').value}}
diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.scss b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.scss index 424a05db49..4fa8f6fe02 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.scss +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.scss @@ -18,6 +18,10 @@ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + + .name { + font-weight: 500; + } } .checkbox-column-title { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts index bca6d927d9..7e5136804b 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device/lwm2m/lwm2m-observe-attr-telemetry-resources.component.ts @@ -105,6 +105,8 @@ export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValue this.resourcesFormGroup.disable({emitEvent: false}); } else { this.resourcesFormArray.controls.forEach(resource => { + resource.get('id').enable({emitEvent: false}); + resource.get('name').enable({emitEvent: false}); resource.get('keyName').enable({emitEvent: false}); resource.get('attribute').enable({emitEvent: false}); resource.get('telemetry').enable({onlySelf: true}); @@ -124,7 +126,7 @@ export class Lwm2mObserveAttrTelemetryResourcesComponent implements ControlValue } getNameResourceLwm2m(resourceLwM2M: ResourceLwM2M): string { - return `<${resourceLwM2M.id}> ${resourceLwM2M.name}`; + return `#${resourceLwM2M.id} ${resourceLwM2M.name}`; } private updatedResources(resources: ResourceLwM2M[]): void { 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 6fc379a56c..9fae435055 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 @@ -22,7 +22,7 @@ [formGroupName]="$index"> - {{ objectLwM2M.get('name').value}} <{{ objectLwM2M.get('id').value}}> + {{ objectLwM2M.get('name').value}} #{{ objectLwM2M.get('keyId').value}} { - return `${objectLwM2M.name} <${objectLwM2M.id}>`; + return `${objectLwM2M.name} #${objectLwM2M.keyId}`; } } 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 bf6b576952..5e8864f233 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1233,7 +1233,7 @@ "instances-input": "Input Instance Id value", "instances-input-holder": "Input Instance number...", "instance": "Instance", - "resource-label": " Resource name", + "resource-label": "#ID Resource name", "observe-label": "Observe", "attribute-label": "Attribute", "telemetry-label": "Telemetry", From 517a1d7edf201d68c3957dc39f4ef8b166189b3f Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 2 Jul 2021 12:52:38 +0300 Subject: [PATCH 205/231] Update rule nodes UI --- .../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 34be66807f..c9668e9381 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,H=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 G,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"}(G||(G={})),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=H,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=H,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
\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
\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=G,n.fetchModes=Object.keys(G),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===G.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),He=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),Ge=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,He,Ge],imports:[r.CommonModule,a.SharedModule,ue],exports:[Me,we,Re,De,Oe,Ke,Be,He,Ge]})],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=He,e.ɵcd=Ge,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,O=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 D,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"}(D||(D={}));var B,H=new Map([[D.METER,"tb.rulenode.range-unit-meter"],[D.KILOMETER,"tb.rulenode.range-unit-kilometer"],[D.FOOT,"tb.rulenode.range-unit-foot"],[D.MILE,"tb.rulenode.range-unit-mile"],[D.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 G,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"}(G||(G={})),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=O,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=H,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 \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(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.pushToEdgeConfigForm},r.prototype.onConfigurationSet=function(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-push-to-edge-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return b(r,e),r.prototype.configForm=function(){return this.pushToCloudConfigForm},r.prototype.onConfigurationSet=function(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=h([t.Component({selector:"tb-action-node-push-to-cloud-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n'}),C("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=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,qe,Se],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,qe,Se]})],e)}(),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}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),Ne=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),Ve=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=O,n.rangeUnits=Object.keys(D),n.rangeUnitTranslationMap=H,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),Ee=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),Ae=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),Le=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),Pe=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),Me=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),we=function(){function e(){}return e=h([t.NgModule({declarations:[ke,Ne,Ve,Ee,Ae,Le,Pe,Me],imports:[r.CommonModule,a.SharedModule,ue],exports:[ke,Ne,Ve,Ee,Ae,Le,Pe,Me]})],e)}(),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.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),Oe=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),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.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
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),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}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
\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.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),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.fetchMode=G,n.fetchModes=Object.keys(G),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===G.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),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.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),Ue=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),je=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),ze=function(){function e(){}return e=h([t.NgModule({declarations:[Re,Oe,De,Ke,Be,He,Ge,Ue,je],imports:[r.CommonModule,a.SharedModule,ue],exports:[Re,Oe,De,Ke,Be,He,Ge,Ue,je]})],e)}(),Qe=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),_e=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),$e=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),We=function(){function e(){}return e=h([t.NgModule({declarations:[Qe,_e,$e],imports:[r.CommonModule,a.SharedModule,ue],exports:[Qe,_e,$e]})],e)}(),Je=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:[Ie,we,ze,We,x]}),C("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=Je,e.ɵa=x,e.ɵb=Ie,e.ɵba=ve,e.ɵbb=Fe,e.ɵbc=xe,e.ɵbd=Te,e.ɵbe=qe,e.ɵbf=Se,e.ɵbg=ue,e.ɵbh=ae,e.ɵbi=oe,e.ɵbj=ie,e.ɵbk=le,e.ɵbl=se,e.ɵbm=me,e.ɵbn=we,e.ɵbo=ke,e.ɵbp=Ne,e.ɵbq=Ve,e.ɵbr=Ee,e.ɵbs=Ae,e.ɵbt=Le,e.ɵbu=Pe,e.ɵbv=Me,e.ɵbw=ze,e.ɵbx=Re,e.ɵby=Oe,e.ɵbz=De,e.ɵc=T,e.ɵca=Ke,e.ɵcb=Be,e.ɵcc=He,e.ɵcd=Ge,e.ɵce=Ue,e.ɵcf=je,e.ɵcg=We,e.ɵch=Qe,e.ɵci=_e,e.ɵcj=$e,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 17f6a4e4f6244eabc9a2ae170ca1453433f7d69f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 2 Jul 2021 14:41:52 +0300 Subject: [PATCH 206/231] Ignore missing properties to simplify updates after minor improvemetns --- .../common/data/device/data/lwm2m/OtherConfiguration.java | 2 ++ 1 file changed, 2 insertions(+) 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 c940cd5e85..1a9f2784da 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 @@ -15,10 +15,12 @@ */ package org.thingsboard.server.common.data.device.data.lwm2m; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import org.thingsboard.server.common.data.device.data.PowerMode; @Data +@JsonIgnoreProperties(ignoreUnknown = true) public class OtherConfiguration { private Integer fwUpdateStrategy; From aa569ceec170bf43b6a098ae7c78f4f3ebcb0561 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 1 Jul 2021 15:29:22 +0300 Subject: [PATCH 207/231] added fw/sw update tests --- .../lwm2m/AbstractLwM2MIntegrationTest.java | 21 +- .../lwm2m/NoSecLwM2MIntegrationTest.java | 156 +++++++++ .../lwm2m/RpkLwM2MIntegrationTest.java | 1 + .../transport/lwm2m/client/FwLwM2MDevice.java | 152 +++++++++ .../lwm2m/client/LwM2MTestClient.java | 6 +- .../lwm2m/client/SimpleLwM2MDevice.java | 54 +-- .../transport/lwm2m/client/SwLwM2MDevice.java | 155 +++++++++ application/src/test/resources/lwm2m/5.xml | 204 +++++++++++ application/src/test/resources/lwm2m/9.xml | 321 ++++++++++++++++++ .../lwm2m/server/client/LwM2mClient.java | 1 - .../ota/DefaultLwM2MOtaUpdateService.java | 2 +- .../store/TbLwM2MClientOtaInfoStore.java | 2 - 12 files changed, 1040 insertions(+), 35 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java create mode 100644 application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java create mode 100644 application/src/test/resources/lwm2m/5.xml create mode 100644 application/src/test/resources/lwm2m/9.xml 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 02ac3a56c6..13bd61fcf3 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 @@ -20,7 +20,6 @@ import org.apache.commons.io.IOUtils; import org.eclipse.californium.core.network.config.NetworkConfig; import org.eclipse.leshan.client.object.Security; import org.eclipse.leshan.core.util.Hex; -import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -85,6 +84,7 @@ import java.util.concurrent.ScheduledExecutorService; 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; +import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; @DaoSqlTest public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { @@ -264,7 +264,7 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { executor = Executors.newScheduledThreadPool(10); loginTenantAdmin(); - String[] resources = new String[]{"1.xml", "2.xml", "3.xml"}; + String[] resources = new String[]{"1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; for (String resourceName : resources) { TbResource lwModel = new TbResource(); lwModel.setResourceType(ResourceType.LWM2M_MODEL); @@ -300,7 +300,6 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { Assert.assertNotNull(deviceProfile); } - @NotNull protected Device createDevice(LwM2MClientCredentials clientCredentials) throws Exception { Device device = new Device(); device.setName("Device A"); @@ -340,6 +339,22 @@ public class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { return savaData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksum={checksum}&checksumAlgorithm={checksumAlgorithm}", testData, CHECKSUM, "SHA256"); } + protected OtaPackageInfo createSoftware() throws Exception { + String CHECKSUM = "4bf5122f344554c53bde2ebb8cd2b7e3d1600ad631c385a5d7cce23c7785459a"; + + OtaPackageInfo swInfo = new OtaPackageInfo(); + swInfo.setDeviceProfileId(deviceProfile.getId()); + swInfo.setType(SOFTWARE); + swInfo.setTitle("My sw"); + swInfo.setVersion("v1.0"); + + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", swInfo, 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); 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 8c20eace51..06f0adafba 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 @@ -15,11 +15,15 @@ */ package org.thingsboard.server.transport.lwm2m; +import com.fasterxml.jackson.core.type.TypeReference; import org.junit.Assert; import org.junit.Test; 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.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataQuery; @@ -32,11 +36,99 @@ 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 java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.stream.Collectors; + +import static org.thingsboard.rest.client.utils.RestJsonConverter.toTimeseries; +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.INITIATED; +import static org.thingsboard.server.common.data.ota.OtaPackageUpdateStatus.QUEUED; +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; public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + private final String OTA_TRANSPORT_CONFIGURATION = "{\n" + + " \"observeAttr\": {\n" + + " \"keyName\": {\n" + + " \"/5_1.0/0/3\": \"state\",\n" + + " \"/5_1.0/0/5\": \"updateResult\",\n" + + " \"/5_1.0/0/6\": \"pkgname\",\n" + + " \"/5_1.0/0/7\": \"pkgversion\",\n" + + " \"/5_1.0/0/9\": \"firmwareUpdateDeliveryMethod\",\n" + + " \"/9_1.0/0/0\": \"pkgname\",\n" + + " \"/9_1.0/0/1\": \"pkgversion\",\n" + + " \"/9_1.0/0/7\": \"updateState\",\n" + + " \"/9_1.0/0/9\": \"updateResult\"\n" + + " },\n" + + " \"observe\": [\n" + + " \"/5_1.0/0/3\",\n" + + " \"/5_1.0/0/5\",\n" + + " \"/5_1.0/0/6\",\n" + + " \"/5_1.0/0/7\",\n" + + " \"/5_1.0/0/9\",\n" + + " \"/9_1.0/0/0\",\n" + + " \"/9_1.0/0/1\",\n" + + " \"/9_1.0/0/7\",\n" + + " \"/9_1.0/0/9\"\n" + + " ],\n" + + " \"attribute\": [],\n" + + " \"telemetry\": [\n" + + " \"/5_1.0/0/3\",\n" + + " \"/5_1.0/0/5\",\n" + + " \"/5_1.0/0/6\",\n" + + " \"/5_1.0/0/7\",\n" + + " \"/5_1.0/0/9\",\n" + + " \"/9_1.0/0/0\",\n" + + " \"/9_1.0/0/1\",\n" + + " \"/9_1.0/0/7\",\n" + + " \"/9_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" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " },\n" + + " \"bootstrapServer\": {\n" + + " \"host\": \"localhost\",\n" + + " \"port\": 5687,\n" + + " \"serverId\": 111,\n" + + " \"securityMode\": \"NO_SEC\",\n" + + " \"serverPublicKey\": \"\",\n" + + " \"clientHoldOffTime\": 1,\n" + + " \"bootstrapServerAccountTimeout\": 0\n" + + " }\n" + + " },\n" + + " \"clientLwM2mSettings\": {\n" + + " \"fwUpdateStrategy\": 1,\n" + + " \"swUpdateStrategy\": 1,\n" + + " \"clientOnlyObserveAfterConnect\": 1,\n" + + " \"powerMode\": \"PSM\",\n" + + " \"fwUpdateResource\": \"\",\n" + + " \"swUpdateResource\": \"\",\n" + + " \"compositeOperationsSupport\": false\n" + + " },\n" + + " \"type\": \"LWM2M\"\n" + + "}"; + @Test public void testConnectAndObserveTelemetry() throws Exception { NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); @@ -94,4 +186,68 @@ public class NoSecLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { client.destroy(); } + @Test + public void testFirmwareUpdateByObject5() throws Exception { + createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); + clientCredentials.setEndpoint("OTA_" + ENDPOINT); + Device device = createDevice(clientCredentials); + + OtaPackageInfo firmware = createFirmware(); + + LwM2MTestClient client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT); + client.init(SECURITY, COAP_CONFIG); + + Thread.sleep(1000); + + device.setFirmwareId(firmware.getId()); + + device = doPost("/api/device", device, Device.class); + + Thread.sleep(4000); + + List ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?orderBy=ASC&keys=fw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { + })); + + List statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); + + List expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADED, UPDATING, UPDATED); + + Assert.assertEquals(expectedStatuses, statuses); + + client.destroy(); + } + + @Test + public void testSoftwareUpdateByObject9() throws Exception { + createDeviceProfile(OTA_TRANSPORT_CONFIGURATION); + NoSecClientCredentials clientCredentials = new NoSecClientCredentials(); + clientCredentials.setEndpoint("OTA_" + ENDPOINT); + Device device = createDevice(clientCredentials); + + OtaPackageInfo software = createSoftware(); + + LwM2MTestClient client = new LwM2MTestClient(executor, "OTA_" + ENDPOINT); + client.init(SECURITY, COAP_CONFIG); + + Thread.sleep(1000); + + device.setSoftwareId(software.getId()); + + device = doPost("/api/device", device, Device.class); + + Thread.sleep(4000); + + List ts = toTimeseries(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/values/timeseries?orderBy=ASC&keys=sw_state&startTs=0&endTs=" + System.currentTimeMillis(), new TypeReference<>() { + })); + + List statuses = ts.stream().map(KvEntry::getValueAsString).map(OtaPackageUpdateStatus::valueOf).collect(Collectors.toList()); + + List expectedStatuses = Arrays.asList(QUEUED, INITIATED, DOWNLOADING, DOWNLOADING, DOWNLOADING, DOWNLOADED, VERIFIED, UPDATED); + + Assert.assertEquals(expectedStatuses, statuses); + + client.destroy(); + } + } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.java index eb81f91e2c..ff3ad1b308 100644 --- a/application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/RpkLwM2MIntegrationTest.java @@ -23,6 +23,7 @@ import org.thingsboard.server.common.data.device.credentials.lwm2m.RPKClientCred import static org.eclipse.leshan.client.object.Security.rpk; public class RpkLwM2MIntegrationTest extends AbstractLwM2MIntegrationTest { + @Test public void testConnectWithRPKAndObserveTelemetry() throws Exception { RPKClientCredentials rpkClientCredentials = new RPKClientCredentials(); diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java new file mode 100644 index 0000000000..5c2b51ea4f --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/FwLwM2MDevice.java @@ -0,0 +1,152 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.util.Arrays; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class FwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { + + private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 5, 6, 7, 9); + + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + private final AtomicInteger state = new AtomicInteger(0); + + private final AtomicInteger updateResult = new AtomicInteger(0); + + @Override + public ReadResponse read(ServerIdentity identity, int resourceId) { + if (!identity.isSystem()) + log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); + switch (resourceId) { + case 3: + return ReadResponse.success(resourceId, getState()); + case 5: + return ReadResponse.success(resourceId, getUpdateResult()); + case 6: + return ReadResponse.success(resourceId, getPkgName()); + case 7: + return ReadResponse.success(resourceId, getPkgVersion()); + case 9: + return ReadResponse.success(resourceId, getFirmwareUpdateDeliveryMethod()); + 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 : ""); + + switch (resourceId) { + case 2: + startUpdating(); + return ExecuteResponse.success(); + default: + return super.execute(identity, resourceId, params); + } + } + + @Override + public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); + + switch (resourceId) { + case 0: + startDownloading(); + return WriteResponse.success(); + case 1: + startDownloading(); + return WriteResponse.success(); + default: + return super.write(identity, replace, resourceId, value); + } + } + + private int getState() { + return state.get(); + } + + private int getUpdateResult() { + return updateResult.get(); + } + + private String getPkgName() { + return "firmware"; + } + + private String getPkgVersion() { + return "1.0.0"; + } + + private int getFirmwareUpdateDeliveryMethod() { + return 1; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + scheduler.shutdownNow(); + } + + private void startDownloading() { + scheduler.schedule(() -> { + state.set(1); + fireResourcesChange(3); + state.set(2); + fireResourcesChange(3); + }, 100, TimeUnit.MILLISECONDS); + } + + private void startUpdating() { + scheduler.schedule(() -> { + try { + state.set(3); + fireResourcesChange(3); + Thread.sleep(100); + updateResult.set(1); + fireResourcesChange(5); + } catch (Exception e) { + + } + }, 100, TimeUnit.MILLISECONDS); + } + +} 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 index 4ab03fa6c0..c4f22ff5bb 100644 --- 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 @@ -53,8 +53,10 @@ import java.util.List; import java.util.concurrent.ScheduledExecutorService; import static org.eclipse.leshan.core.LwM2mId.DEVICE; +import static org.eclipse.leshan.core.LwM2mId.FIRMWARE; import static org.eclipse.leshan.core.LwM2mId.SECURITY; import static org.eclipse.leshan.core.LwM2mId.SERVER; +import static org.eclipse.leshan.core.LwM2mId.SOFTWARE_MANAGEMENT; @Slf4j @Data @@ -65,7 +67,7 @@ public class LwM2MTestClient { private LeshanClient client; public void init(Security security, NetworkConfig coapConfig) throws InvalidDDFFileException, IOException { - String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml"}; + String[] resources = new String[]{"0.xml", "1.xml", "2.xml", "3.xml", "5.xml", "9.xml"}; List models = new ArrayList<>(); for (String resourceName : resources) { models.addAll(ObjectLoader.loadDdfFile(LwM2MTestClient.class.getClassLoader().getResourceAsStream("lwm2m/" + resourceName), resourceName)); @@ -75,6 +77,8 @@ public class LwM2MTestClient { initializer.setInstancesForObject(SECURITY, security); initializer.setInstancesForObject(SERVER, new Server(123, 300)); initializer.setInstancesForObject(DEVICE, new SimpleLwM2MDevice()); + initializer.setInstancesForObject(FIRMWARE, new FwLwM2MDevice()); + initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, new SwLwM2MDevice()); initializer.setClassForObject(LwM2mId.ACCESS_CONTROL, DummyInstanceEnabler.class); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(); 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 index 9c9741b0a3..9ede9da80e 100644 --- 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 @@ -45,74 +45,74 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl ); @Override - public ReadResponse read(ServerIdentity identity, int resourceid) { + public ReadResponse read(ServerIdentity identity, int resourceId) { if (!identity.isSystem()) - log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); - switch (resourceid) { + log.info("Read on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); + switch (resourceId) { case 0: - return ReadResponse.success(resourceid, getManufacturer()); + return ReadResponse.success(resourceId, getManufacturer()); case 1: - return ReadResponse.success(resourceid, getModelNumber()); + return ReadResponse.success(resourceId, getModelNumber()); case 2: - return ReadResponse.success(resourceid, getSerialNumber()); + return ReadResponse.success(resourceId, getSerialNumber()); case 3: - return ReadResponse.success(resourceid, getFirmwareVersion()); + return ReadResponse.success(resourceId, getFirmwareVersion()); case 9: - return ReadResponse.success(resourceid, getBatteryLevel()); + return ReadResponse.success(resourceId, getBatteryLevel()); case 10: - return ReadResponse.success(resourceid, getMemoryFree()); + return ReadResponse.success(resourceId, getMemoryFree()); case 11: Map errorCodes = new HashMap<>(); errorCodes.put(0, getErrorCode()); - return ReadResponse.success(resourceid, errorCodes, ResourceModel.Type.INTEGER); + return ReadResponse.success(resourceId, errorCodes, ResourceModel.Type.INTEGER); case 14: - return ReadResponse.success(resourceid, getUtcOffset()); + return ReadResponse.success(resourceId, getUtcOffset()); case 15: - return ReadResponse.success(resourceid, getTimezone()); + return ReadResponse.success(resourceId, getTimezone()); case 16: - return ReadResponse.success(resourceid, getSupportedBinding()); + return ReadResponse.success(resourceId, getSupportedBinding()); case 17: - return ReadResponse.success(resourceid, getDeviceType()); + return ReadResponse.success(resourceId, getDeviceType()); case 18: - return ReadResponse.success(resourceid, getHardwareVersion()); + return ReadResponse.success(resourceId, getHardwareVersion()); case 19: - return ReadResponse.success(resourceid, getSoftwareVersion()); + return ReadResponse.success(resourceId, getSoftwareVersion()); case 20: - return ReadResponse.success(resourceid, getBatteryStatus()); + return ReadResponse.success(resourceId, getBatteryStatus()); case 21: - return ReadResponse.success(resourceid, getMemoryTotal()); + return ReadResponse.success(resourceId, getMemoryTotal()); default: - return super.read(identity, resourceid); + return super.read(identity, resourceId); } } @Override - public ExecuteResponse execute(ServerIdentity identity, int resourceid, String params) { + 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 : ""); + log.info("Execute on Device resource /{}/{}/{} {}", getModel().id, getId(), resourceId, withParams != null ? withParams : ""); return ExecuteResponse.success(); } @Override - public WriteResponse write(ServerIdentity identity, boolean replace, int resourceid, LwM2mResource value) { - log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceid); + public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); - switch (resourceid) { + switch (resourceId) { case 13: return WriteResponse.notFound(); case 14: setUtcOffset((String) value.getValue()); - fireResourcesChange(resourceid); + fireResourcesChange(resourceId); return WriteResponse.success(); case 15: setTimezone((String) value.getValue()); - fireResourcesChange(resourceid); + fireResourcesChange(resourceId); return WriteResponse.success(); default: - return super.write(identity, replace, resourceid, value); + return super.write(identity, replace, resourceId, value); } } diff --git a/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java new file mode 100644 index 0000000000..8d8f07d542 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SwLwM2MDevice.java @@ -0,0 +1,155 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF 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.SneakyThrows; +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.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.util.Arrays; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +@Slf4j +public class SwLwM2MDevice extends BaseInstanceEnabler implements Destroyable { + + private static final List supportedResources = Arrays.asList(0, 1, 2, 3, 4, 6, 7, 9); + + private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); + + private final AtomicInteger state = new AtomicInteger(0); + + private final AtomicInteger updateResult = new AtomicInteger(0); + + @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, getPkgName()); + case 1: + return ReadResponse.success(resourceId, getPkgVersion()); + case 7: + return ReadResponse.success(resourceId, getUpdateState()); + case 9: + return ReadResponse.success(resourceId, getUpdateResult()); + 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 : ""); + + switch (resourceId) { + case 4: + startUpdating(); + return ExecuteResponse.success(); + case 6: + return ExecuteResponse.success(); + default: + return super.execute(identity, resourceId, params); + } + } + + @Override + public WriteResponse write(ServerIdentity identity, boolean replace, int resourceId, LwM2mResource value) { + log.info("Write on Device resource /{}/{}/{}", getModel().id, getId(), resourceId); + + switch (resourceId) { + case 2: + startDownloading(); + return WriteResponse.success(); + case 3: + startDownloading(); + return WriteResponse.success(); + default: + return super.write(identity, replace, resourceId, value); + } + } + + private int getUpdateState() { + return state.get(); + } + + private int getUpdateResult() { + return updateResult.get(); + } + + private String getPkgName() { + return "software"; + } + + private String getPkgVersion() { + return "1.0.0"; + } + + @Override + public List getAvailableResourceIds(ObjectModel model) { + return supportedResources; + } + + @Override + public void destroy() { + scheduler.shutdownNow(); + } + + private void startDownloading() { + scheduler.schedule(() -> { + try { + state.set(1); + updateResult.set(1); + fireResourcesChange(7, 9); + Thread.sleep(100); + state.set(2); + fireResourcesChange(7); + Thread.sleep(100); + state.set(3); + fireResourcesChange(7); + Thread.sleep(100); + updateResult.set(3); + fireResourcesChange(9); + } catch (Exception e) { + + } + }, 100, TimeUnit.MILLISECONDS); + } + + private void startUpdating() { + scheduler.schedule(() -> { + state.set(4); + updateResult.set(2); + fireResourcesChange(7, 9); + }, 100, TimeUnit.MILLISECONDS); + } + +} diff --git a/application/src/test/resources/lwm2m/5.xml b/application/src/test/resources/lwm2m/5.xml new file mode 100644 index 0000000000..5133f7d9ab --- /dev/null +++ b/application/src/test/resources/lwm2m/5.xml @@ -0,0 +1,204 @@ + + + + + + + Firmware Update V_1_0 + + 5 + urn:oma:lwm2m:oma:5 + 1.0 + 1.0 + Single + Optional + + + Package + W + Single + Mandatory + Opaque + + + + + + Package URI + RW + Single + Mandatory + String + 0..255 + + + + + Update + E + Single + Mandatory + + + + + + + State + R + Single + Mandatory + Integer + 0..3 + + + + + Update Result + R + Single + Mandatory + Integer + 0..9 + + + + + PkgName + R + Single + Optional + String + 0..255 + + + + + PkgVersion + R + Single + Optional + String + 0..255 + + + + + Firmware Update Protocol Support + R + Multiple + Optional + Integer + 0..5 + + + + + Firmware Update Delivery Method + R + Single + Mandatory + Integer + 0..2 + + + + + + + diff --git a/application/src/test/resources/lwm2m/9.xml b/application/src/test/resources/lwm2m/9.xml new file mode 100644 index 0000000000..4186b1248d --- /dev/null +++ b/application/src/test/resources/lwm2m/9.xml @@ -0,0 +1,321 @@ + + + + + + + LWM2M Software Management + + 9 + urn:oma:lwm2m:oma:9 + 1.0 + 1.0 + Multiple + Optional + + + PkgName + R + Single + Mandatory + String + 0..255 bytes + + + + + PkgVersion + R + Single + Mandatory + String + 0..255 bytes + + + + + Package + W + Single + Optional + Opaque + + + + + + Package URI + W + Single + Optional + String + 0..255 bytes + + + + + + Install + E + Single + Mandatory + + + + + + + Checkpoint + R + Single + Optional + Objlnk + + + + + + Uninstall + E + Single + Mandatory + + + + + + + Update State + R + Single + Mandatory + Integer + 0..4 + + + + + Update Supported Objects + RW + Single + Optional + Boolean + + + + + + Update Result + R + Single + Mandatory + Integer + 0..200 + + + + + Activate + E + Single + Mandatory + + + + + + + Deactivate + E + Single + Mandatory + + + + + + + Activation State + R + Single + Mandatory + Boolean + + + + + + Package Settings + RW + Single + Optional + Objlnk + + + + + + User Name + W + Single + Optional + String + 0..255 bytes + + + + + Password + W + Single + Optional + String + 0..255 bytes + + + + + Status Reason + R + Single + Optional + String + + + + + + Software Component Link + R + Multiple + Optional + Objlnk + + + + + + Software Component tree length + R + Single + Optional + Integer + 0..255 + + + + + + + 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 d88e0131b1..2662560d08 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 @@ -158,7 +158,6 @@ public class LwM2mClient implements Serializable { this.session = builder.build(); } - private SessionInfoProto createSession(String nodeId, UUID sessionId, ValidateDeviceCredentialsResponse msg) { return SessionInfoProto.newBuilder() .setNodeId(nodeId) 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 fd0fbf4a71..3074061b5b 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 @@ -337,7 +337,7 @@ public class DefaultLwM2MOtaUpdateService extends LwM2MExecutorAwareService impl SoftwareUpdateResult result = SoftwareUpdateResult.fromUpdateResultSwByCode(code.intValue()); Optional status = toOtaPackageUpdateStatus(result); status.ifPresent(otaStatus -> sendStateUpdateToTelemetry(client, swInfo, - otaStatus, "Firmware Update Result: " + result.name())); + otaStatus, "Software Update Result: " + result.name())); if (result.isAgain() && swInfo.getRetryAttempts() <= 2) { swInfo.setRetryAttempts(swInfo.getRetryAttempts() + 1); startSoftwareUpdateIfNeeded(client, swInfo); diff --git a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientOtaInfoStore.java b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientOtaInfoStore.java index fb7bff4337..dbbb373b0c 100644 --- a/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientOtaInfoStore.java +++ b/common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2MClientOtaInfoStore.java @@ -15,8 +15,6 @@ */ package org.thingsboard.server.transport.lwm2m.server.store; -import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.transport.lwm2m.server.ota.LwM2MClientOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.firmware.LwM2MClientFwOtaInfo; import org.thingsboard.server.transport.lwm2m.server.ota.software.LwM2MClientSwOtaInfo; From 25cac25731e61014b5ad2fa7bc7a20c64bdf6413 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 6 Jul 2021 13:50:26 +0300 Subject: [PATCH 208/231] Minor renaming of variables and code cleanup --- .../DefaultTbRuleEngineConsumerService.java | 27 +++++++++---------- .../src/main/resources/thingsboard.yml | 6 ++--- 2 files changed, 15 insertions(+), 18 deletions(-) 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 64c5d03dce..2edaceee8e 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 @@ -82,7 +82,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< public static final String SUCCESSFUL_STATUS = "successful"; public static final String FAILED_STATUS = "failed"; - public static final String THREAD_TOPIC_SPLITERATOR = " | "; + public static final String THREAD_TOPIC_SEPARATOR = " | "; @Value("${queue.rule-engine.poll-interval}") private long pollDuration; @Value("${queue.rule-engine.pack-processing-timeout}") @@ -145,9 +145,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< @PreDestroy public void stop() { super.destroy(); - if (submitExecutor != null) { - submitExecutor.shutdownNow(); - } + submitExecutor.shutdownNow(); ruleEngineSettings.getQueues().forEach(config -> consumerConfigurations.put(config.getName(), config)); } @@ -174,11 +172,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< repartitionExecutor.schedule(() -> repartitionTopicWithConsumerPerPartition(queue), 1, TimeUnit.SECONDS); } - void repartitionTopicWithConsumerPerPartition(final String queue) { + void repartitionTopicWithConsumerPerPartition(final String queueName) { if (stopped) { return; } - TbTopicWithConsumerPerPartition tbTopicWithConsumerPerPartition = topicsConsumerPerPartition.get(queue); + TbTopicWithConsumerPerPartition tbTopicWithConsumerPerPartition = topicsConsumerPerPartition.get(queueName); Queue> subscribeQueue = tbTopicWithConsumerPerPartition.getSubscribeQueue(); if (subscribeQueue.isEmpty()) { return; @@ -203,16 +201,15 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< log.info("calculated removedPartitions {}", removedPartitions); removedPartitions.forEach((tpi) -> { - removeConsumerForTopicByTpi(queue, consumers, tpi); + removeConsumerForTopicByTpi(queueName, consumers, tpi); }); addedPartitions.forEach((tpi) -> { - log.info("[{}] Adding consumer for topic: {}", queue, tpi); - TbRuleEngineQueueConfiguration configuration = consumerConfigurations.get(queue); - //consumerStats.computeIfAbsent(queue, queueName -> new TbRuleEngineConsumerStats(configuration.getName(), statsFactory)); //already created on init + log.info("[{}] Adding consumer for topic: {}", queueName, tpi); + TbRuleEngineQueueConfiguration configuration = consumerConfigurations.get(queueName); TbQueueConsumer> consumer = tbRuleEngineQueueFactory.createToRuleEngineMsgConsumer(configuration); consumers.put(tpi, consumer); - launchConsumer(consumer, consumerConfigurations.get(queue), consumerStats.get(queue), "" + queue + "-" + tpi.getPartition().orElse(-999999)); + launchConsumer(consumer, consumerConfigurations.get(queueName), consumerStats.get(queueName), "" + queueName + "-" + tpi.getPartition().orElse(-999999)); consumer.subscribe(Collections.singleton(tpi)); }); @@ -220,7 +217,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< tbTopicWithConsumerPerPartition.getLock().unlock(); } } else { - scheduleTopicRepartition(queue); //reschedule later + scheduleTopicRepartition(queueName); //reschedule later } } @@ -240,7 +237,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< protected void stopMainConsumers() { consumers.values().forEach(TbQueueConsumer::unsubscribe); topicsConsumerPerPartition.values().forEach(tbTopicWithConsumerPerPartition -> tbTopicWithConsumerPerPartition.getConsumers().keySet() - .forEach((tpi)-> removeConsumerForTopicByTpi(tbTopicWithConsumerPerPartition.getTopic(), tbTopicWithConsumerPerPartition.getConsumers(), tpi))); + .forEach((tpi) -> removeConsumerForTopicByTpi(tbTopicWithConsumerPerPartition.getTopic(), tbTopicWithConsumerPerPartition.getConsumers(), tpi))); } void launchConsumer(TbQueueConsumer> consumer, TbRuleEngineQueueConfiguration configuration, TbRuleEngineConsumerStats stats, String threadSuffix) { @@ -304,11 +301,11 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< void updateCurrentThreadName(String threadSuffix) { String name = Thread.currentThread().getName(); - int spliteratorIndex = name.indexOf(THREAD_TOPIC_SPLITERATOR); + int spliteratorIndex = name.indexOf(THREAD_TOPIC_SEPARATOR); if (spliteratorIndex > 0) { name = name.substring(0, spliteratorIndex); } - name = name + THREAD_TOPIC_SPLITERATOR + threadSuffix; + name = name + THREAD_TOPIC_SEPARATOR + threadSuffix; Thread.currentThread().setName(name); } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index ddce060f43..05f3f1e825 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -866,7 +866,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}" - consumer-per-partition: "${TB_QUEUE_RE_MAIN_CONSUMER_PER_PARTITION:false}" + consumer-per-partition: "${TB_QUEUE_RE_MAIN_CONSUMER_PER_PARTITION:true}" 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 @@ -883,7 +883,7 @@ queue: 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}" - consumer-per-partition: "${TB_QUEUE_RE_HP_CONSUMER_PER_PARTITION:false}" + consumer-per-partition: "${TB_QUEUE_RE_HP_CONSUMER_PER_PARTITION:true}" 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 @@ -900,7 +900,7 @@ queue: 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}" - consumer-per-partition: "${TB_QUEUE_RE_SQ_CONSUMER_PER_PARTITION:false}" + consumer-per-partition: "${TB_QUEUE_RE_SQ_CONSUMER_PER_PARTITION:true}" 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 From 513fc8bd3e53b69f0d767e20e603c843195a33d3 Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 30 Apr 2021 14:36:34 +0300 Subject: [PATCH 209/231] Ensure sessions max size per device on handle activity process --- .../device/DeviceActorMessageProcessor.java | 62 ++++++++++++++----- .../server/actors/device/SessionInfo.java | 1 - 2 files changed, 48 insertions(+), 15 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 4d41b258f3..fd3155d662 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 @@ -96,6 +96,8 @@ import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.function.Consumer; @@ -110,7 +112,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { final TenantId tenantId; final DeviceId deviceId; - private final Map sessions; + private final LinkedHashMap sessions; private final Map attributeSubscriptions; private final Map rpcSubscriptions; private final Map toDeviceRpcPendingMap; @@ -552,18 +554,16 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { private void processSessionStateMsgs(SessionInfoProto sessionInfo, SessionEventMsg msg) { UUID sessionId = getSessionId(sessionInfo); + Objects.requireNonNull(sessionId); if (msg.getEvent() == SessionEvent.OPEN) { if (sessions.containsKey(sessionId)) { log.debug("[{}] Received duplicate session open event [{}]", deviceId, sessionId); return; } - log.debug("[{}] Processing new session [{}]", deviceId, sessionId); - if (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) { - UUID sessionIdToRemove = sessions.keySet().stream().findFirst().orElse(null); - if (sessionIdToRemove != null) { - notifyTransportAboutClosedSession(sessionIdToRemove, sessions.remove(sessionIdToRemove), "max concurrent sessions limit reached per device!"); - } - } + log.info("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size()); + + ensureSessionsCapacity(); + sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId()))); if (sessions.size() == 1) { reportSessionOpen(); @@ -582,10 +582,26 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } } + private void ensureSessionsCapacity() { + while (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) { + Optional sessionIdToRemove = sessions.keySet().stream().findFirst(); + if (sessionIdToRemove.isPresent()) { + notifyTransportAboutClosedSession(sessionIdToRemove.get(), sessions.remove(sessionIdToRemove.get()), "max concurrent sessions limit reached per device!"); + } else { + log.warn("[{}] Can't remove session because find first returns null", deviceId); + } + } + log.debug("[{}] sessions size after clean up {}", deviceId, sessions.size()); + } + private void handleSessionActivity(TbActorCtx context, SessionInfoProto sessionInfoProto, SubscriptionInfoProto subscriptionInfo) { UUID sessionId = getSessionId(sessionInfoProto); + Objects.requireNonNull(sessionId); + + ensureSessionsCapacity(); + SessionInfoMetaData sessionMD = sessions.computeIfAbsent(sessionId, - id -> new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()), 0L)); + id -> new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()), subscriptionInfo.getLastActivityTime())); sessionMD.setLastActivityTime(subscriptionInfo.getLastActivityTime()); sessionMD.setSubscribedToAttributes(subscriptionInfo.getAttributeSubscription()); @@ -762,6 +778,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } private void restoreSessions() { + sessions.clear(); log.debug("[{}] Restoring sessions from cache", deviceId); DeviceSessionsCacheEntry sessionsDump = null; try { @@ -774,6 +791,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { log.debug("[{}] No session information found", deviceId); return; } + // TODO: Take latest max allowed sessions size from cache for (SessionSubscriptionInfoProto sessionSubscriptionInfoProto : sessionsDump.getSessionsList()) { SessionInfoProto sessionInfoProto = sessionSubscriptionInfoProto.getSessionInfo(); UUID sessionId = getSessionId(sessionInfoProto); @@ -792,11 +810,21 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { log.debug("[{}] Restored session: {}", deviceId, sessionMD); } log.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); + + ensureSessionsCapacity(); } private void dumpSessions() { + ensureSessionsCapacity(); + log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); - List sessionsList = new ArrayList<>(sessions.size()); + List sessionsList; + if (sessions.size() < systemContext.getMaxConcurrentSessionsPerDevice()) { + sessionsList = new ArrayList<>(sessions.size()); + } else { + log.warn("[{}] Sessions size {} list more than maxConcurrentSessionsPerDevice {}. Dumping only latest", deviceId, sessions.size(), systemContext.getMaxConcurrentSessionsPerDevice()); + sessionsList = new ArrayList<>((int) systemContext.getMaxConcurrentSessionsPerDevice()); + } sessions.forEach((uuid, sessionMD) -> { if (sessionMD.getSessionInfo().getType() == SessionType.SYNC) { return; @@ -810,10 +838,14 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { .setSessionIdMSB(uuid.getMostSignificantBits()) .setSessionIdLSB(uuid.getLeastSignificantBits()) .setNodeId(sessionInfo.getNodeId()).build(); - sessionsList.add(SessionSubscriptionInfoProto.newBuilder() - .setSessionInfo(sessionInfoProto) - .setSubscriptionInfo(subscriptionInfoProto).build()); - log.debug("[{}] Dumping session: {}", deviceId, sessionMD); + if (sessionsList.size() <= systemContext.getMaxConcurrentSessionsPerDevice()) { + sessionsList.add(SessionSubscriptionInfoProto.newBuilder() + .setSessionInfo(sessionInfoProto) + .setSubscriptionInfo(subscriptionInfoProto).build()); + log.debug("[{}] Dumping session: {}", deviceId, sessionMD); + } else { + log.warn("[{}] Session was not dumped: {}", deviceId, sessionMD); + } }); systemContext.getDeviceSessionCacheService() .put(deviceId, DeviceSessionsCacheEntry.newBuilder() @@ -843,6 +875,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } void checkSessionsTimeout() { + log.info("[{}] checkSessionsTimeout started. Size before check {}", deviceId, sessions.size()); long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout(); Map sessionsToRemove = sessions.entrySet().stream().filter(kv -> kv.getValue().getLastActivityTime() < expTime).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); sessionsToRemove.forEach((sessionId, sessionMD) -> { @@ -854,6 +887,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { if (!sessionsToRemove.isEmpty()) { dumpSessions(); } + log.info("[{}] checkSessionsTimeout finished. Size after check {}", deviceId, sessions.size()); } } diff --git a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java index b42855ace5..24e050e89f 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/SessionInfo.java @@ -25,5 +25,4 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionType; public class SessionInfo { private final SessionType type; private final String nodeId; - private long lastActivityTime; } From 989a2d24f878cfca639082a338171c55e5ead64a Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Fri, 30 Apr 2021 14:39:10 +0300 Subject: [PATCH 210/231] Code clean up --- .../device/DeviceActorMessageProcessor.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 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 fd3155d662..a7959acdc4 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 @@ -778,7 +778,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } private void restoreSessions() { - sessions.clear(); log.debug("[{}] Restoring sessions from cache", deviceId); DeviceSessionsCacheEntry sessionsDump = null; try { @@ -818,13 +817,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { ensureSessionsCapacity(); log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); - List sessionsList; - if (sessions.size() < systemContext.getMaxConcurrentSessionsPerDevice()) { - sessionsList = new ArrayList<>(sessions.size()); - } else { - log.warn("[{}] Sessions size {} list more than maxConcurrentSessionsPerDevice {}. Dumping only latest", deviceId, sessions.size(), systemContext.getMaxConcurrentSessionsPerDevice()); - sessionsList = new ArrayList<>((int) systemContext.getMaxConcurrentSessionsPerDevice()); - } + List sessionsList = new ArrayList<>(sessions.size()); sessions.forEach((uuid, sessionMD) -> { if (sessionMD.getSessionInfo().getType() == SessionType.SYNC) { return; @@ -838,14 +831,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { .setSessionIdMSB(uuid.getMostSignificantBits()) .setSessionIdLSB(uuid.getLeastSignificantBits()) .setNodeId(sessionInfo.getNodeId()).build(); - if (sessionsList.size() <= systemContext.getMaxConcurrentSessionsPerDevice()) { - sessionsList.add(SessionSubscriptionInfoProto.newBuilder() - .setSessionInfo(sessionInfoProto) - .setSubscriptionInfo(subscriptionInfoProto).build()); - log.debug("[{}] Dumping session: {}", deviceId, sessionMD); - } else { - log.warn("[{}] Session was not dumped: {}", deviceId, sessionMD); - } + sessionsList.add(SessionSubscriptionInfoProto.newBuilder() + .setSessionInfo(sessionInfoProto) + .setSubscriptionInfo(subscriptionInfoProto).build()); + log.debug("[{}] Dumping session: {}", deviceId, sessionMD); }); systemContext.getDeviceSessionCacheService() .put(deviceId, DeviceSessionsCacheEntry.newBuilder() From 5f0bd38816346bb32d1657c7a5426986c834b8ff Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Wed, 5 May 2021 10:40:15 +0300 Subject: [PATCH 211/231] Fixed log level --- .../server/actors/device/DeviceActorMessageProcessor.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 a7959acdc4..0df5fcaaa5 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 @@ -864,7 +864,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } void checkSessionsTimeout() { - log.info("[{}] checkSessionsTimeout started. Size before check {}", deviceId, sessions.size()); + log.debug("[{}] checkSessionsTimeout started. Size before check {}", deviceId, sessions.size()); long expTime = System.currentTimeMillis() - systemContext.getSessionInactivityTimeout(); Map sessionsToRemove = sessions.entrySet().stream().filter(kv -> kv.getValue().getLastActivityTime() < expTime).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); sessionsToRemove.forEach((sessionId, sessionMD) -> { @@ -876,7 +876,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { if (!sessionsToRemove.isEmpty()) { dumpSessions(); } - log.info("[{}] checkSessionsTimeout finished. Size after check {}", deviceId, sessions.size()); + log.debug("[{}] checkSessionsTimeout finished. Size after check {}", deviceId, sessions.size()); } } From d7f797a0650958cbee5e5e39a0f571190237fce5 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 18 May 2021 12:15:14 +0300 Subject: [PATCH 212/231] util: LinkedHashMapRemoveEldest implemented with test --- common/util/pom.xml | 5 ++ .../util/LinkedHashMapRemoveEldest.java | 83 +++++++++++++++++++ .../util/LinkedHashMapRemoveEldestTest.java | 70 ++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java create mode 100644 common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java diff --git a/common/util/pom.xml b/common/util/pom.xml index 8a0fa1705b..26c4b42920 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -74,6 +74,11 @@ junit test + + org.hamcrest + hamcrest + test + org.mockito mockito-core diff --git a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java new file mode 100644 index 0000000000..7f91da737b --- /dev/null +++ b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java @@ -0,0 +1,83 @@ +/** + * 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 lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * HashMap that removed eldest (by put order) entries + * It guaranteed that size is not greater then maxEntries parameter. And remove time is constant O(1). + * Use withMaxEntries to setup maxEntries. + * Because overloaded constructor will look similar to LinkedHashMap(initCapacity) + * Example: + * new LinkedHashMapRemoveEldest().withMaxEntries(100L) + * */ +@Getter +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +public class LinkedHashMapRemoveEldest extends LinkedHashMap { + long maxEntries = Long.MAX_VALUE; + + public LinkedHashMapRemoveEldest() { + super(); + } + + public LinkedHashMapRemoveEldest(int initialCapacity) { + super(initialCapacity); + } + + public LinkedHashMapRemoveEldest(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); + } + + public LinkedHashMapRemoveEldest(Map m) { + super(m); + } + + public LinkedHashMapRemoveEldest(int initialCapacity, float loadFactor, boolean accessOrder) { + super(initialCapacity, loadFactor, accessOrder); + } + + public LinkedHashMapRemoveEldest withMaxEntries(long maxEntries) { + this.maxEntries = maxEntries; + return this; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxEntries; + } +} diff --git a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java new file mode 100644 index 0000000000..4dad070ec4 --- /dev/null +++ b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java @@ -0,0 +1,70 @@ +/** + * 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.junit.Before; +import org.junit.Test; + +import java.util.LinkedHashMap; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class LinkedHashMapRemoveEldestTest { + + public static final long MAX_ENTRIES = 10L; + LinkedHashMapRemoveEldest map; + + @Before + public void setUp() throws Exception { + map = new LinkedHashMapRemoveEldest().withMaxEntries(MAX_ENTRIES); + } + + @Test + public void givenMap_whenOverSized_thenVerifyRemovedEldest() { + assertThat(map.getMaxEntries(), is(MAX_ENTRIES)); + assertThat(map, instanceOf(LinkedHashMap.class)); + assertThat(map, instanceOf(LinkedHashMapRemoveEldest.class)); + assertThat(map.size(), is(0)); + + for (long i = 0; i < MAX_ENTRIES * 2; i++) { + map.put(i, String.valueOf(i)); + } + + assertThat(map.size(), is((int) MAX_ENTRIES)); + + for (long i = MAX_ENTRIES; i < MAX_ENTRIES * 2; i++) { + assertThat(map.get(i), is(String.valueOf(i))); + } + } + +} \ No newline at end of file From 3d3aae60d8e72600bf62efee391e7522b898e37b Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 18 May 2021 13:07:53 +0300 Subject: [PATCH 213/231] util: LinkedHashMapRemoveEldest refactored with BiConsumer --- .../util/LinkedHashMapRemoveEldest.java | 45 +++++++------------ .../util/LinkedHashMapRemoveEldestTest.java | 21 ++++++--- 2 files changed, 29 insertions(+), 37 deletions(-) diff --git a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java index 7f91da737b..c1e28ca4c9 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java +++ b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java @@ -36,48 +36,33 @@ import lombok.ToString; import java.util.LinkedHashMap; import java.util.Map; +import java.util.function.BiConsumer; /** - * HashMap that removed eldest (by put order) entries + * LinkedHashMap that removed eldest entries (by insert order) * It guaranteed that size is not greater then maxEntries parameter. And remove time is constant O(1). - * Use withMaxEntries to setup maxEntries. - * Because overloaded constructor will look similar to LinkedHashMap(initCapacity) * Example: - * new LinkedHashMapRemoveEldest().withMaxEntries(100L) + * LinkedHashMapRemoveEldest map = + * new LinkedHashMapRemoveEldest<>(MAX_ENTRIES, this::removeConsumer); * */ @Getter @ToString(callSuper = true) @EqualsAndHashCode(callSuper = true) -public class LinkedHashMapRemoveEldest extends LinkedHashMap { - long maxEntries = Long.MAX_VALUE; +public class LinkedHashMapRemoveEldest extends LinkedHashMap { + final long maxEntries; + final BiConsumer removeConsumer; - public LinkedHashMapRemoveEldest() { - super(); - } - - public LinkedHashMapRemoveEldest(int initialCapacity) { - super(initialCapacity); - } - - public LinkedHashMapRemoveEldest(int initialCapacity, float loadFactor) { - super(initialCapacity, loadFactor); - } - - public LinkedHashMapRemoveEldest(Map m) { - super(m); - } - - public LinkedHashMapRemoveEldest(int initialCapacity, float loadFactor, boolean accessOrder) { - super(initialCapacity, loadFactor, accessOrder); - } - - public LinkedHashMapRemoveEldest withMaxEntries(long maxEntries) { + public LinkedHashMapRemoveEldest(long maxEntries, BiConsumer removeConsumer) { this.maxEntries = maxEntries; - return this; + this.removeConsumer = removeConsumer; } @Override - protected boolean removeEldestEntry(Map.Entry eldest) { - return size() > maxEntries; + protected boolean removeEldestEntry(Map.Entry eldest) { + if (size() <= maxEntries) { + return false; + } + removeConsumer.accept(eldest.getKey(), eldest.getValue()); + return true; } } diff --git a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java index 4dad070ec4..62d7f788af 100644 --- a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java +++ b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java @@ -30,7 +30,7 @@ */ package org.thingsboard.common.util; -import org.junit.Before; +import org.hamcrest.Matchers; import org.junit.Test; import java.util.LinkedHashMap; @@ -42,26 +42,33 @@ import static org.hamcrest.MatcherAssert.assertThat; public class LinkedHashMapRemoveEldestTest { public static final long MAX_ENTRIES = 10L; - LinkedHashMapRemoveEldest map; + long removeCount = 0; - @Before - public void setUp() throws Exception { - map = new LinkedHashMapRemoveEldest().withMaxEntries(MAX_ENTRIES); + void removeConsumer(Long id, String name) { + removeCount++; + assertThat(id, is(Matchers.lessThan(MAX_ENTRIES))); + assertThat(name, is(id.toString())); } @Test public void givenMap_whenOverSized_thenVerifyRemovedEldest() { + //given + LinkedHashMapRemoveEldest map = + new LinkedHashMapRemoveEldest<>(MAX_ENTRIES, this::removeConsumer); + assertThat(map.getMaxEntries(), is(MAX_ENTRIES)); assertThat(map, instanceOf(LinkedHashMap.class)); assertThat(map, instanceOf(LinkedHashMapRemoveEldest.class)); assertThat(map.size(), is(0)); + //when for (long i = 0; i < MAX_ENTRIES * 2; i++) { map.put(i, String.valueOf(i)); } - assertThat(map.size(), is((int) MAX_ENTRIES)); - + //then + assertThat((long) map.size(), is(MAX_ENTRIES)); + assertThat(removeCount, is(MAX_ENTRIES)); for (long i = MAX_ENTRIES; i < MAX_ENTRIES * 2; i++) { assertThat(map.get(i), is(String.valueOf(i))); } From 57b0e19ec853b62f0bed89675c849cc1a194abc0 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 18 May 2021 14:28:59 +0300 Subject: [PATCH 214/231] util: LinkedHashMapRemoveEldest refactored --- .../common/util/LinkedHashMapRemoveEldest.java | 8 ++++---- .../common/util/LinkedHashMapRemoveEldestTest.java | 6 ++++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java index c1e28ca4c9..42a4d26d87 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java +++ b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java @@ -50,11 +50,11 @@ import java.util.function.BiConsumer; @EqualsAndHashCode(callSuper = true) public class LinkedHashMapRemoveEldest extends LinkedHashMap { final long maxEntries; - final BiConsumer removeConsumer; + final BiConsumer removalConsumer; - public LinkedHashMapRemoveEldest(long maxEntries, BiConsumer removeConsumer) { + public LinkedHashMapRemoveEldest(long maxEntries, BiConsumer removalConsumer) { this.maxEntries = maxEntries; - this.removeConsumer = removeConsumer; + this.removalConsumer = removalConsumer; } @Override @@ -62,7 +62,7 @@ public class LinkedHashMapRemoveEldest extends LinkedHashMap { if (size() <= maxEntries) { return false; } - removeConsumer.accept(eldest.getKey(), eldest.getValue()); + removalConsumer.accept(eldest.getKey(), eldest.getValue()); return true; } } diff --git a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java index 62d7f788af..3107aa8b07 100644 --- a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java +++ b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java @@ -37,6 +37,7 @@ import java.util.LinkedHashMap; import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; public class LinkedHashMapRemoveEldestTest { @@ -44,7 +45,7 @@ public class LinkedHashMapRemoveEldestTest { public static final long MAX_ENTRIES = 10L; long removeCount = 0; - void removeConsumer(Long id, String name) { + void removalConsumer(Long id, String name) { removeCount++; assertThat(id, is(Matchers.lessThan(MAX_ENTRIES))); assertThat(name, is(id.toString())); @@ -54,9 +55,10 @@ public class LinkedHashMapRemoveEldestTest { public void givenMap_whenOverSized_thenVerifyRemovedEldest() { //given LinkedHashMapRemoveEldest map = - new LinkedHashMapRemoveEldest<>(MAX_ENTRIES, this::removeConsumer); + new LinkedHashMapRemoveEldest<>(MAX_ENTRIES, this::removalConsumer); assertThat(map.getMaxEntries(), is(MAX_ENTRIES)); + assertThat(map.getRemovalConsumer(), notNullValue()); assertThat(map, instanceOf(LinkedHashMap.class)); assertThat(map, instanceOf(LinkedHashMapRemoveEldest.class)); assertThat(map.size(), is(0)); From a44230a253fb47eb5d3be8663ff115bf10372afb Mon Sep 17 00:00:00 2001 From: Sergey Matvienko <50422128+handsome-serg@users.noreply.github.com> Date: Tue, 18 May 2021 14:32:07 +0300 Subject: [PATCH 215/231] DeviceActorMessageProcessor: introduced LinkedHashMapRemoveEldest with BiConsumer instead many ensureSessionsCapacity() that spreaded over the class. test added --- .../device/DeviceActorMessageProcessor.java | 29 ++-------- .../DeviceActorMessageProcessorTest.java | 58 +++++++++++++++++++ 2 files changed, 63 insertions(+), 24 deletions(-) create mode 100644 application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java 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 0df5fcaaa5..f4e25f7de2 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 @@ -24,6 +24,7 @@ import com.google.protobuf.InvalidProtocolBufferException; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.LinkedHashMapRemoveEldest; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.rule.engine.api.msg.DeviceCredentialsUpdateNotificationMsg; @@ -112,7 +113,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { final TenantId tenantId; final DeviceId deviceId; - private final LinkedHashMap sessions; + final LinkedHashMapRemoveEldest sessions; private final Map attributeSubscriptions; private final Map rpcSubscriptions; private final Map toDeviceRpcPendingMap; @@ -127,16 +128,16 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { super(systemContext); this.tenantId = tenantId; this.deviceId = deviceId; - this.sessions = new LinkedHashMap<>(); this.attributeSubscriptions = new HashMap<>(); this.rpcSubscriptions = new HashMap<>(); this.toDeviceRpcPendingMap = new HashMap<>(); + this.sessions = new LinkedHashMapRemoveEldest<>(systemContext.getMaxConcurrentSessionsPerDevice(), this::notifyTransportAboutClosedSession); if (initAttributes()) { restoreSessions(); } } - private boolean initAttributes() { + boolean initAttributes() { Device device = systemContext.getDeviceService().findDeviceById(tenantId, deviceId); if (device != null) { this.deviceName = device.getName(); @@ -562,8 +563,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } log.info("[{}] Processing new session [{}]. Current sessions size {}", deviceId, sessionId, sessions.size()); - ensureSessionsCapacity(); - sessions.put(sessionId, new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfo.getNodeId()))); if (sessions.size() == 1) { reportSessionOpen(); @@ -582,24 +581,10 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } } - private void ensureSessionsCapacity() { - while (sessions.size() >= systemContext.getMaxConcurrentSessionsPerDevice()) { - Optional sessionIdToRemove = sessions.keySet().stream().findFirst(); - if (sessionIdToRemove.isPresent()) { - notifyTransportAboutClosedSession(sessionIdToRemove.get(), sessions.remove(sessionIdToRemove.get()), "max concurrent sessions limit reached per device!"); - } else { - log.warn("[{}] Can't remove session because find first returns null", deviceId); - } - } - log.debug("[{}] sessions size after clean up {}", deviceId, sessions.size()); - } - private void handleSessionActivity(TbActorCtx context, SessionInfoProto sessionInfoProto, SubscriptionInfoProto subscriptionInfo) { UUID sessionId = getSessionId(sessionInfoProto); Objects.requireNonNull(sessionId); - ensureSessionsCapacity(); - SessionInfoMetaData sessionMD = sessions.computeIfAbsent(sessionId, id -> new SessionInfoMetaData(new SessionInfo(SessionType.ASYNC, sessionInfoProto.getNodeId()), subscriptionInfo.getLastActivityTime())); @@ -777,7 +762,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { return builder.build(); } - private void restoreSessions() { + void restoreSessions() { log.debug("[{}] Restoring sessions from cache", deviceId); DeviceSessionsCacheEntry sessionsDump = null; try { @@ -809,13 +794,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { log.debug("[{}] Restored session: {}", deviceId, sessionMD); } log.debug("[{}] Restored sessions: {}, rpc subscriptions: {}, attribute subscriptions: {}", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); - - ensureSessionsCapacity(); } private void dumpSessions() { - ensureSessionsCapacity(); - log.debug("[{}] Dumping sessions: {}, rpc subscriptions: {}, attribute subscriptions: {} to cache", deviceId, sessions.size(), rpcSubscriptions.size(), attributeSubscriptions.size()); List sessionsList = new ArrayList<>(sessions.size()); sessions.forEach((uuid, sessionMD) -> { diff --git a/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java b/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java new file mode 100644 index 0000000000..9565dd71f4 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java @@ -0,0 +1,58 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.actors.device; + +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.common.util.LinkedHashMapRemoveEldest; +import org.thingsboard.server.actors.ActorSystemContext; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.device.DeviceService; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; + +public class DeviceActorMessageProcessorTest { + + public static final long MAX_CONCURRENT_SESSIONS_PER_DEVICE = 10L; + ActorSystemContext systemContext; + DeviceService deviceService; + TenantId tenantId = TenantId.SYS_TENANT_ID; + DeviceId deviceId = DeviceId.fromString("78bf9b26-74ef-4af2-9cfb-ad6cf24ad2ec"); + + DeviceActorMessageProcessor processor; + + @Before + public void setUp() { + systemContext = mock(ActorSystemContext.class); + deviceService = mock(DeviceService.class); + willReturn(MAX_CONCURRENT_SESSIONS_PER_DEVICE).given(systemContext).getMaxConcurrentSessionsPerDevice(); + willReturn(deviceService).given(systemContext).getDeviceService(); + processor = new DeviceActorMessageProcessor(systemContext, tenantId, deviceId); + } + + @Test + public void givenSystemContext_whenNewInstance_thenVerifySessionMapMaxSize() { + assertThat(processor.sessions, instanceOf(LinkedHashMapRemoveEldest.class)); + assertThat(processor.sessions.getMaxEntries(), is(MAX_CONCURRENT_SESSIONS_PER_DEVICE)); + assertThat(processor.sessions.getRemovalConsumer(), notNullValue()); + } +} \ No newline at end of file From 4eb6a94ff5ab735e2fc92e317f08a9af05d8c895 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko <50422128+handsome-serg@users.noreply.github.com> Date: Sun, 27 Jun 2021 17:03:02 +0300 Subject: [PATCH 216/231] fixed licence header (CE) for LinkedHashMapRemoveEldest --- .../util/LinkedHashMapRemoveEldest.java | 41 ++++++------------- .../util/LinkedHashMapRemoveEldestTest.java | 41 ++++++------------- 2 files changed, 26 insertions(+), 56 deletions(-) diff --git a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java index 42a4d26d87..d1bf556256 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java +++ b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java @@ -1,32 +1,17 @@ /** - * 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. + * Copyright © 2016-2021 The Thingsboard Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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; diff --git a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java index 3107aa8b07..5cd75b388a 100644 --- a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java +++ b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java @@ -1,32 +1,17 @@ /** - * 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. + * Copyright © 2016-2021 The Thingsboard Authors + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS 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 66f5e17babd69aea0fcb584d668c4500aa05cf70 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko <50422128+handsome-serg@users.noreply.github.com> Date: Sun, 27 Jun 2021 17:09:52 +0300 Subject: [PATCH 217/231] fixed licence header (CE) for LinkedHashMapRemoveEldest (removed

) --- .../common/util/LinkedHashMapRemoveEldest.java | 8 ++++---- .../common/util/LinkedHashMapRemoveEldestTest.java | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java index d1bf556256..a877c123f7 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java +++ b/common/util/src/main/java/org/thingsboard/common/util/LinkedHashMapRemoveEldest.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2021 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java index 5cd75b388a..263cbf5efb 100644 --- a/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java +++ b/common/util/src/test/java/org/thingsboard/common/util/LinkedHashMapRemoveEldestTest.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2021 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. From 89fe9514d14f2860416b6ee8833bd7cb2367c7df Mon Sep 17 00:00:00 2001 From: Sergey Matvienko <50422128+handsome-serg@users.noreply.github.com> Date: Mon, 28 Jun 2021 00:19:04 +0300 Subject: [PATCH 218/231] DeviceActorMessageProcessor: fixed message on MaxSessionsLimit and test header fixed --- .../server/actors/device/DeviceActorMessageProcessor.java | 7 ++++++- .../actors/device/DeviceActorMessageProcessorTest.java | 8 ++++---- 2 files changed, 10 insertions(+), 5 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 f4e25f7de2..ec4b79408b 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 @@ -131,7 +131,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { this.attributeSubscriptions = new HashMap<>(); this.rpcSubscriptions = new HashMap<>(); this.toDeviceRpcPendingMap = new HashMap<>(); - this.sessions = new LinkedHashMapRemoveEldest<>(systemContext.getMaxConcurrentSessionsPerDevice(), this::notifyTransportAboutClosedSession); + this.sessions = new LinkedHashMapRemoveEldest<>(systemContext.getMaxConcurrentSessionsPerDevice(), this::notifyTransportAboutClosedSessionMaxSessionsLimit); if (initAttributes()) { restoreSessions(); } @@ -616,6 +616,11 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { } } + private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) { + notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!"); + } + + private void notifyTransportAboutClosedSession(UUID sessionId, SessionInfoMetaData sessionMd, String message) { SessionCloseNotificationProto sessionCloseNotificationProto = SessionCloseNotificationProto .newBuilder() diff --git a/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java b/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java index 9565dd71f4..753ad53d90 100644 --- a/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java +++ b/application/src/test/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessorTest.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2021 The Thingsboard Authors - *

+ * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. From 2880dcf526a6d5155a27ef5ceeb6cb8766dbe998 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko <50422128+handsome-serg@users.noreply.github.com> Date: Tue, 8 Jun 2021 17:34:30 +0300 Subject: [PATCH 219/231] device cache by id --- .../server/dao/device/DeviceServiceImpl.java | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) 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 f1470ff5ad..899f976448 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 @@ -27,6 +27,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.cache.annotation.Caching; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -147,6 +148,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceDao.findDeviceInfoById(tenantId, deviceId.getId()); } + @Cacheable(cacheNames = DEVICE_CACHE, key = "{#tenantId, #deviceId}") @Override public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceById [{}]", deviceId); @@ -178,13 +180,19 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe return deviceOpt.orElse(null); } - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}") + @Caching(evict= { + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"), + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}") + }) @Override public Device saveDeviceWithAccessToken(Device device, String accessToken) { return doSaveDevice(device, accessToken); } - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}") + @Caching(evict= { + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"), + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}") + }) @Override public Device saveDevice(Device device) { return doSaveDevice(device, null); @@ -521,7 +529,10 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Transactional - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}") + @Caching(evict= { + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.name}"), + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#device.tenantId, #device.id}") + }) @Override public Device assignDeviceToTenant(TenantId tenantId, Device device) { log.trace("Executing assignDeviceToTenant [{}][{}]", tenantId, device); @@ -546,7 +557,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Override - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}") + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}") //id is not available yet @Transactional public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = new Device(); From 2287b05d9903fefe29b5a74141fa1e063023a3b9 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko <50422128+handsome-serg@users.noreply.github.com> Date: Tue, 8 Jun 2021 17:58:34 +0300 Subject: [PATCH 220/231] device cache: removeDeviceFromCacheById added --- .../server/dao/device/DeviceServiceImpl.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) 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 899f976448..78b41285c6 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 @@ -258,6 +258,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("device_name_unq_key")) { // remove device from cache in case null value cached in the distributed redis. removeDeviceFromCacheByName(device.getTenantId(), device.getName()); + removeDeviceFromCacheById(device.getTenantId(), device.getId()); throw new DataValidationException("Device with such name already exists!"); } else { throw t; @@ -335,6 +336,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe deleteEntityRelations(tenantId, deviceId); removeDeviceFromCacheByName(tenantId, device.getName()); + removeDeviceFromCacheById(tenantId, device.getId()); deviceDao.removeById(tenantId, deviceId.getId()); } @@ -344,6 +346,11 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe cache.evict(Arrays.asList(tenantId, name)); } + private void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) { + Cache cache = cacheManager.getCache(DEVICE_CACHE); + cache.evict(Arrays.asList(tenantId, deviceId)); + } + @Override public PageData findDevicesByTenantId(TenantId tenantId, PageLink pageLink) { log.trace("Executing findDevicesByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); @@ -557,7 +564,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } @Override - @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}") //id is not available yet + @CacheEvict(cacheNames = DEVICE_CACHE, key = "{#profile.tenantId, #provisionRequest.deviceName}") @Transactional public Device saveDevice(ProvisionRequest provisionRequest, DeviceProfile profile) { Device device = new Device(); @@ -599,6 +606,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe throw new ProvisionFailedException(ProvisionResponseStatus.FAILURE.name()); } } + removeDeviceFromCacheById(savedDevice.getTenantId(), savedDevice.getId()); return savedDevice; } @@ -688,6 +696,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } if (!old.getName().equals(device.getName())) { removeDeviceFromCacheByName(tenantId, old.getName()); + removeDeviceFromCacheById(tenantId, old.getId()); } } From 759e8ee3c7341f80bc859ff728828890361065fd Mon Sep 17 00:00:00 2001 From: Volodymyr Babak Date: Tue, 8 Jun 2021 18:21:19 +0300 Subject: [PATCH 221/231] Device cache improvements during code review --- .../org/thingsboard/server/dao/device/DeviceServiceImpl.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 78b41285c6..174e8e529d 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 @@ -347,6 +347,9 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } private void removeDeviceFromCacheById(TenantId tenantId, DeviceId deviceId) { + if (deviceId == null) { + return; + } Cache cache = cacheManager.getCache(DEVICE_CACHE); cache.evict(Arrays.asList(tenantId, deviceId)); } @@ -696,7 +699,6 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe } if (!old.getName().equals(device.getName())) { removeDeviceFromCacheByName(tenantId, old.getName()); - removeDeviceFromCacheById(tenantId, old.getId()); } } From 3fc45654e852270d9c68a0e60b5fb72320f059ea Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 2 Jul 2021 14:39:46 +0300 Subject: [PATCH 222/231] fixed search for resources --- .../server/dao/sql/resource/TbResourceInfoRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java index db1c5273c8..1c3f0eda74 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/resource/TbResourceInfoRepository.java @@ -27,7 +27,7 @@ import java.util.UUID; public interface TbResourceInfoRepository extends CrudRepository { @Query("SELECT tr FROM TbResourceInfoEntity tr WHERE " + - "LOWER(tr.searchText) LIKE LOWER(CONCAT(:searchText, '%'))" + + "LOWER(tr.title) LIKE LOWER(CONCAT('%', :searchText, '%'))" + "AND (tr.tenantId = :tenantId " + "OR (tr.tenantId = :systemAdminId " + "AND NOT EXISTS " + @@ -42,7 +42,7 @@ public interface TbResourceInfoRepository extends CrudRepository findTenantResourcesByTenantId(@Param("tenantId") UUID tenantId, @Param("searchText") String searchText, Pageable pageable); From 29d04ef79295680c20f0db5182d4eabeb6239fbc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 2 Jul 2021 15:13:27 +0300 Subject: [PATCH 223/231] UI: Fixed didn't tooltip show in Map widget at Safari browser --- .../home/components/widget/lib/maps/providers/google-map.ts | 3 ++- .../home/components/widget/lib/maps/providers/here-map.ts | 3 ++- .../home/components/widget/lib/maps/providers/image-map.ts | 3 ++- .../components/widget/lib/maps/providers/openstreet-map.ts | 3 ++- .../home/components/widget/lib/maps/providers/tencent-map.ts | 5 +++-- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts index 0ec78090cd..f3369dd46c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts @@ -38,7 +38,8 @@ export class GoogleMap extends LeafletMap { this.loadGoogle(() => { const map = L.map($container, { attributionControl: false, - editable: !!options.editablePolygon + editable: !!options.editablePolygon, + tap: L.Browser.safari && L.Browser.mobile }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); (L.gridLayer as any).googleMutant({ type: options?.gmDefaultMapType || 'roadmap' diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts index 9dd93b6fd9..e15c897c3b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts @@ -23,7 +23,8 @@ export class HEREMap extends LeafletMap { constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { super(ctx, $container, options); const map = L.map($container, { - editable: !!options.editablePolygon + editable: !!options.editablePolygon, + tap: L.Browser.safari && L.Browser.mobile }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials); tileLayer.addTo(map); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index fe386036fd..272476ea9e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -221,7 +221,8 @@ export class ImageMap extends LeafletMap { zoom: 1, crs: L.CRS.Simple, attributionControl: false, - editable: !!this.options.editablePolygon + editable: !!this.options.editablePolygon, + tap: L.Browser.safari && L.Browser.mobile }); this.updateBounds(updateImage); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts index 019eb3ab3c..61312aab23 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts @@ -23,7 +23,8 @@ export class OpenStreetMap extends LeafletMap { constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { super(ctx, $container, options); const map = L.map($container, { - editable: !!options.editablePolygon + editable: !!options.editablePolygon, + tap: L.Browser.safari && L.Browser.mobile }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); let tileLayer; if (options.useCustomProvider) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts index 1d764b6625..bd34e24331 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts @@ -25,12 +25,13 @@ export class TencentMap extends LeafletMap { super(ctx, $container, options); const txUrl = 'http://rt{s}.map.gtimg.com/realtimerender?z={z}&x={x}&y={y}&type=vector&style=0'; const map = L.map($container, { - editable: !!options.editablePolygon + editable: !!options.editablePolygon, + tap: L.Browser.safari && L.Browser.mobile }).setView(options?.defaultCenterPosition, options?.defaultZoomLevel || DEFAULT_ZOOM_LEVEL); const txLayer = L.tileLayer(txUrl, { subdomains: '0123', tms: true, - attribution: '©2020 Tencent - GS(2018)2236号- Data© NavInfo' + attribution: '©2021 Tencent - GS(2020)2236号- Data© NavInfo' }).addTo(map); txLayer.addTo(map); super.initSettings(options); From 7c20121337f3ae188351c20e7b469851dc642595 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 2 Jul 2021 15:31:49 +0300 Subject: [PATCH 224/231] UI [MAP]: Fixed parse data for datasource type function --- .../home/components/widget/lib/maps/common-maps-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts index 32a9074ca0..9b517bfab7 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts @@ -319,7 +319,7 @@ export const parseWithTranslation = { }; export function parseData(input: DatasourceData[]): FormattedData[] { - return _(input).groupBy(el => el?.datasource.entityId + el?.datasource.entityType) + return _(input).groupBy(el => el?.datasource.entityName + el?.datasource.entityType) .values().value().map((entityArray, i) => { const obj: FormattedData = { entityName: entityArray[0]?.datasource?.entityName, From 8b70cbdb551bc3cded37ed7532cfab7a3f495967 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Fri, 2 Jul 2021 11:32:23 +0300 Subject: [PATCH 225/231] Handling exceptions to exclude output like [object Object] --- .../exception/ThingsboardErrorResponseHandler.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index ed8c0a7a17..28f0c38eee 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -26,8 +26,10 @@ import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.msg.tools.TbRateLimitsException; @@ -40,14 +42,15 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -@Component @Slf4j -public class ThingsboardErrorResponseHandler implements AccessDeniedHandler { +@RestControllerAdvice +public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHandler implements AccessDeniedHandler { @Autowired private ObjectMapper mapper; @Override + @ExceptionHandler(AccessDeniedException.class) public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { @@ -60,6 +63,7 @@ public class ThingsboardErrorResponseHandler implements AccessDeniedHandler { } } + @ExceptionHandler(Exception.class) public void handle(Exception exception, HttpServletResponse response) { log.debug("Processing exception {}", exception.getMessage(), exception); if (!response.isCommitted()) { @@ -159,7 +163,7 @@ public class ThingsboardErrorResponseHandler implements AccessDeniedHandler { } else if (authenticationException instanceof AuthMethodNotSupportedException) { mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of(authenticationException.getMessage(), ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); } else if (authenticationException instanceof UserPasswordExpiredException) { - UserPasswordExpiredException expiredException = (UserPasswordExpiredException)authenticationException; + UserPasswordExpiredException expiredException = (UserPasswordExpiredException) authenticationException; String resetToken = expiredException.getResetToken(); mapper.writeValue(response.getWriter(), ThingsboardCredentialsExpiredResponse.of(expiredException.getMessage(), resetToken)); } else { From 7975e2a96a1a463be2215b07cd0aa03a0d33b1ed Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Tue, 1 Jun 2021 16:07:18 +0300 Subject: [PATCH 226/231] Implement of method to find dashboard by name and tenant ID, used to find correct dashboard in case multiple dashboard start with the same name. --- .../security/auth/oauth2/AbstractOAuth2ClientMapper.java | 7 +------ .../thingsboard/server/dao/dashboard/DashboardService.java | 2 ++ .../thingsboard/server/dao/dashboard/DashboardInfoDao.java | 2 ++ .../server/dao/dashboard/DashboardServiceImpl.java | 6 +++++- .../server/dao/sql/dashboard/DashboardInfoRepository.java | 3 +++ .../server/dao/sql/dashboard/JpaDashboardInfoDao.java | 5 +++++ 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 3ff3534806..024b262357 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -31,7 +31,6 @@ 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.id.DashboardId; -import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; @@ -206,11 +205,7 @@ public abstract class AbstractOAuth2ClientMapper { } private Optional getDashboardId(TenantId tenantId, String dashboardName) { - PageLink searchTextLink = new PageLink(1, 0, dashboardName); - PageData dashboardsPage = dashboardService.findDashboardsByTenantId(tenantId, searchTextLink); - return dashboardsPage.getData().stream() - .findAny() - .map(IdBased::getId); + return Optional.of(dashboardService.findFirstDashboardInfoByTenantIdAndName(tenantId, dashboardName).getId()); } private Optional getDashboardId(TenantId tenantId, CustomerId customerId, String dashboardName) { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java index fc3eaf9a12..b9ac0e2afc 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java @@ -58,4 +58,6 @@ public interface DashboardService { Dashboard unassignDashboardFromEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId); PageData findDashboardsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink); + + DashboardInfo findFirstDashboardInfoByTenantIdAndName(TenantId tenantId, String name); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java index 22a4567198..ad1ff3d450 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java @@ -56,4 +56,6 @@ public interface DashboardInfoDao extends Dao { */ PageData findDashboardsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink); + DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java index 7c6150ba3a..ba4716853c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java @@ -34,7 +34,6 @@ 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.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; @@ -269,6 +268,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb return dashboardInfoDao.findDashboardsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink); } + @Override + public DashboardInfo findFirstDashboardInfoByTenantIdAndName(TenantId tenantId, String name) { + return dashboardInfoDao.findFirstByTenantIdAndName(tenantId.getId(), name); + } + private DataValidator dashboardValidator = new DataValidator() { @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index 6bbf3a1d61..6150b43c0f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -29,6 +29,9 @@ import java.util.UUID; */ public interface DashboardInfoRepository extends PagingAndSortingRepository { + DashboardInfoEntity findFirstByTenantIdAndTitle(@Param("tenantId") UUID tenantId, + @Param("name") String title); + @Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " + "AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") Page findByTenantId(@Param("tenantId") UUID tenantId, diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java index 4c34f6148a..109539577c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java @@ -83,4 +83,9 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao Date: Tue, 1 Jun 2021 16:55:02 +0300 Subject: [PATCH 227/231] Improvements for case null returned from repository --- .../security/auth/oauth2/AbstractOAuth2ClientMapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index 024b262357..6fa8c45620 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -31,6 +31,7 @@ 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.id.DashboardId; +import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2MapperConfig; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; @@ -205,7 +206,7 @@ public abstract class AbstractOAuth2ClientMapper { } private Optional getDashboardId(TenantId tenantId, String dashboardName) { - return Optional.of(dashboardService.findFirstDashboardInfoByTenantIdAndName(tenantId, dashboardName).getId()); + return Optional.ofNullable(dashboardService.findFirstDashboardInfoByTenantIdAndName(tenantId, dashboardName)).map(IdBased::getId); } private Optional getDashboardId(TenantId tenantId, CustomerId customerId, String dashboardName) { From 4ab3db85fc2253f1c34800a9a4d5720c31dcd161 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Tue, 6 Jul 2021 14:14:47 +0300 Subject: [PATCH 228/231] Redundant params removed --- .../server/dao/sql/dashboard/DashboardInfoRepository.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java index 6150b43c0f..a725c112e3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java @@ -29,8 +29,7 @@ import java.util.UUID; */ public interface DashboardInfoRepository extends PagingAndSortingRepository { - DashboardInfoEntity findFirstByTenantIdAndTitle(@Param("tenantId") UUID tenantId, - @Param("name") String title); + DashboardInfoEntity findFirstByTenantIdAndTitle(UUID tenantId, String title); @Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " + "AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") From 449845fe517b0a91f1fd5aa8725e86ae16b88245 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard Date: Tue, 1 Jun 2021 17:32:12 +0300 Subject: [PATCH 229/231] No body for REST API Call Node on GET method --- .../org/thingsboard/rule/engine/rest/TbHttpClient.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 7e6ba50a77..76f10ebb0c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -174,7 +174,13 @@ public class TbHttpClient { String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg); HttpHeaders headers = prepareHeaders(msg); HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); - HttpEntity entity = new HttpEntity<>(msg.getData(), headers); + HttpEntity entity; + if(HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method) || + HttpMethod.OPTIONS.equals(method) || HttpMethod.TRACE.equals(method)) { + entity = new HttpEntity<>(headers); + } else { + entity = new HttpEntity<>(msg.getData(), headers); + } ListenableFuture> future = httpClient.exchange( endpointUrl, method, entity, String.class); From 3712d738552dd31840f92421c5975c7e7e1ba851 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard <77969531+AndrewVolosytnykhThingsboard@users.noreply.github.com> Date: Tue, 6 Jul 2021 17:23:25 +0300 Subject: [PATCH 230/231] Device profile dynamic value Co-authored-by: DanyloKobzar <69635327+DanyloKobzar@users.noreply.github.com> Co-authored-by: Vladyslav_Prykhodko --- .../update/DefaultDataUpdateService.java | 82 ++- .../install/update/PaginatedUpdater.java | 2 +- .../profile/DurationAlarmConditionSpec.java | 3 +- .../profile/RepeatingAlarmConditionSpec.java | 5 +- .../rule/engine/profile/AlarmRuleState.java | 84 ++- .../rule/engine/profile/AlarmState.java | 2 +- .../rule/engine/profile/ProfileState.java | 44 +- .../profile/TbDeviceProfileNodeTest.java | 649 +++++++++++++++++- .../filter-predicate-value.component.ts | 23 +- .../home/components/home-components.module.ts | 3 + ...rm-duration-predicate-value.component.html | 79 +++ ...rm-duration-predicate-value.component.scss | 23 + ...larm-duration-predicate-value.component.ts | 153 +++++ ...alarm-rule-condition-dialog.component.html | 76 +- .../alarm-rule-condition-dialog.component.ts | 31 +- .../alarm/alarm-rule-condition.component.ts | 42 +- ui-ngx/src/app/shared/models/device.models.ts | 5 +- .../app/shared/models/query/query.models.ts | 15 + .../assets/locale/locale.constant-en_US.json | 2 + 19 files changed, 1176 insertions(+), 147 deletions(-) create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss create mode 100644 ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts 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 8c27417f90..11e0edfbe8 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 @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.install.update; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -31,6 +32,7 @@ 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.alarm.AlarmSeverity; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; @@ -41,16 +43,21 @@ import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams; 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.query.DynamicValue; +import org.thingsboard.server.common.data.query.FilterPredicateValue; 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.DaoUtil; 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.model.sql.DeviceProfileEntity; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Utils; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.sql.device.DeviceProfileRepository; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.service.install.InstallScripts; @@ -92,6 +99,9 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired private AlarmDao alarmDao; + @Autowired + private DeviceProfileRepository deviceProfileRepository; + @Autowired private OAuth2Service oAuth2Service; @@ -114,6 +124,7 @@ public class DefaultDataUpdateService implements DataUpdateService { log.info("Updating data from version 3.2.2 to 3.3.0 ..."); tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); tenantsAlarmsCustomerUpdater.updateEntities(null); + deviceProfileEntityDynamicConditionsUpdater.updateEntities(null); updateOAuth2Params(); break; default: @@ -121,6 +132,45 @@ public class DefaultDataUpdateService implements DataUpdateService { } } + private final PaginatedUpdater deviceProfileEntityDynamicConditionsUpdater = + new PaginatedUpdater<>() { + + @Override + protected String getName() { + return "Device Profile Entity Dynamic Conditions Updater"; + } + + @Override + protected PageData findEntities(String id, PageLink pageLink) { + return DaoUtil.pageToPageData(deviceProfileRepository.findAll(DaoUtil.toPageable(pageLink))); + } + + @Override + protected void updateEntity(DeviceProfileEntity deviceProfile) { + if (deviceProfile.getProfileData().has("alarms") && + !deviceProfile.getProfileData().get("alarms").isNull()) { + boolean isUpdated = false; + JsonNode array = deviceProfile.getProfileData().get("alarms"); + for (JsonNode node : array) { + if (node.has("createRules")) { + JsonNode createRules = node.get("createRules"); + for (AlarmSeverity severity : AlarmSeverity.values()) { + if (createRules.has(severity.name())) { + isUpdated = isUpdated || convertDeviceProfileAlarmRulesForVersion330(createRules.get(severity.name()).get("condition").get("spec")); + } + } + } + if (node.has("clearRule") && !node.get("clearRule").isNull()) { + isUpdated = isUpdated || convertDeviceProfileAlarmRulesForVersion330(node.get("clearRule").get("condition").get("spec")); + } + } + if (isUpdated) { + deviceProfileRepository.save(deviceProfile); + } + } + } + }; + private final PaginatedUpdater tenantsDefaultRuleChainUpdater = new PaginatedUpdater<>() { @@ -370,6 +420,33 @@ public class DefaultDataUpdateService implements DataUpdateService { } } + private boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) { + if (spec != null) { + if (spec.has("type") && spec.get("type").asText().equals("DURATION")) { + if (spec.has("value")) { + long value = spec.get("value").asLong(); + var predicate = new FilterPredicateValue<>( + value, null, new DynamicValue<>(null, null, false) + ); + ((ObjectNode) spec).remove("value"); + ((ObjectNode) spec).putPOJO("predicate", predicate); + return true; + } + } else if (spec.has("type") && spec.get("type").asText().equals("REPEATING")) { + if (spec.has("count")) { + int count = spec.get("count").asInt(); + var predicate = new FilterPredicateValue<>( + count, null, new DynamicValue<>(null, null, false) + ); + ((ObjectNode) spec).remove("count"); + ((ObjectNode) spec).putPOJO("predicate", predicate); + return true; + } + } + } + return false; + } + private void updateOAuth2Params() { try { OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params(); @@ -380,9 +457,8 @@ public class DefaultDataUpdateService implements DataUpdateService { oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList())); log.info("Successfully updated OAuth2 parameters!"); } - } - catch (Exception e) { - log.error("Failed to update OAuth2 parameters", e); + } catch (Exception e) { + log.error("Failed to update OAuth2 parameters", e); } } 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 4bfe744863..768d627a16 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 @@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @Slf4j -public abstract class PaginatedUpdater> { +public abstract class PaginatedUpdater { private static final int DEFAULT_LIMIT = 100; private int updated = 0; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java index 7ae5b96ed8..f4f4077971 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; +import org.thingsboard.server.common.data.query.FilterPredicateValue; import java.util.concurrent.TimeUnit; @@ -25,7 +26,7 @@ import java.util.concurrent.TimeUnit; public class DurationAlarmConditionSpec implements AlarmConditionSpec { private TimeUnit unit; - private long value; + private FilterPredicateValue predicate; @Override public AlarmConditionSpecType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java index 0905ef6b94..c7060ee642 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java @@ -17,14 +17,13 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; - -import java.util.concurrent.TimeUnit; +import org.thingsboard.server.common.data.query.FilterPredicateValue; @Data @JsonIgnoreProperties(ignoreUnknown = true) public class RepeatingAlarmConditionSpec implements AlarmConditionSpec { - private int count; + private FilterPredicateValue predicate; @Override public AlarmConditionSpecType getType() { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index 928a403fc4..8ae6390b31 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -24,6 +24,7 @@ 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.AlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule; import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem; @@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; +import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate; @@ -43,6 +45,7 @@ import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.function.Function; @Data @@ -52,8 +55,6 @@ class AlarmRuleState { private final AlarmSeverity severity; private final AlarmRule alarmRule; private final AlarmConditionSpec spec; - private final long requiredDurationInMs; - private final long requiredRepeats; private final Set entityKeys; private PersistedAlarmRuleState state; private boolean updateFlag; @@ -69,20 +70,6 @@ class AlarmRuleState { this.state = new PersistedAlarmRuleState(0L, 0L, 0L); } this.spec = getSpec(alarmRule); - long requiredDurationInMs = 0; - long requiredRepeats = 0; - switch (spec.getType()) { - case DURATION: - DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec; - requiredDurationInMs = duration.getUnit().toMillis(duration.getValue()); - break; - case REPEATING: - RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec; - requiredRepeats = repeating.getCount(); - break; - } - this.requiredDurationInMs = requiredDurationInMs; - this.requiredRepeats = requiredRepeats; this.dynamicPredicateValueCtx = dynamicPredicateValueCtx; } @@ -211,6 +198,7 @@ class AlarmRuleState { if (active && eval(alarmRule.getCondition(), data)) { state.setEventCount(state.getEventCount() + 1); updateFlag = true; + long requiredRepeats = resolveRequiredRepeats(data); return state.getEventCount() >= requiredRepeats ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { return AlarmEvalResult.FALSE; @@ -230,18 +218,62 @@ class AlarmRuleState { state.setDuration(0L); updateFlag = true; } + long requiredDurationInMs = resolveRequiredDurationInMs(data); return state.getDuration() > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; } else { return AlarmEvalResult.FALSE; } } - public AlarmEvalResult eval(long ts) { + private long resolveRequiredRepeats(DataSnapshot data) { + long repeatingTimes = 0; + AlarmConditionSpec alarmConditionSpec = getSpec(); + AlarmConditionSpecType specType = alarmConditionSpec.getType(); + if(specType.equals(AlarmConditionSpecType.REPEATING)) { + RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec; + + repeatingTimes = repeating.getPredicate().getDefaultValue(); + + if (repeating.getPredicate().getDynamicValue() != null && + repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) { + EntityKeyValue repeatingKeyValue = getDynamicPredicateValue(data, repeating.getPredicate().getDynamicValue()); + if (repeatingKeyValue != null) { + repeatingTimes = repeatingKeyValue.getLngValue(); + } + } + } + return repeatingTimes; + } + + private long resolveRequiredDurationInMs(DataSnapshot data) { + long durationTimeInMs = 0; + AlarmConditionSpec alarmConditionSpec = getSpec(); + AlarmConditionSpecType specType = alarmConditionSpec.getType(); + if(specType.equals(AlarmConditionSpecType.DURATION)) { + DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec; + TimeUnit timeUnit = duration.getUnit(); + + durationTimeInMs = timeUnit.toMillis(duration.getPredicate().getDefaultValue()); + + if (duration.getPredicate().getDynamicValue() != null && + duration.getPredicate().getDynamicValue().getSourceAttribute() != null) { + EntityKeyValue durationKeyValue = getDynamicPredicateValue(data, duration.getPredicate().getDynamicValue()); + if (durationKeyValue != null) { + durationTimeInMs = timeUnit.toMillis(durationKeyValue.getLngValue()); + } + } + } + + return durationTimeInMs; + } + + public AlarmEvalResult eval(long ts, DataSnapshot dataSnapshot) { switch (spec.getType()) { case SIMPLE: case REPEATING: return AlarmEvalResult.NOT_YET_TRUE; case DURATION: + long requiredDurationInMs = resolveRequiredDurationInMs(dataSnapshot); if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) { long duration = state.getDuration() + (ts - state.getLastEventTs()); if (isActive(ts)) { @@ -411,7 +443,7 @@ class AlarmRuleState { } private T getPredicateValue(DataSnapshot data, FilterPredicateValue value, AlarmConditionFilter filter, Function transformFunction) { - EntityKeyValue ekv = getDynamicPredicateValue(data, value); + EntityKeyValue ekv = getDynamicPredicateValue(data, value.getDynamicValue()); if (ekv != null) { T result = transformFunction.apply(ekv); if (result != null) { @@ -425,22 +457,22 @@ class AlarmRuleState { } } - private EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue value) { + private EntityKeyValue getDynamicPredicateValue(DataSnapshot data, DynamicValue value) { EntityKeyValue ekv = null; - if (value.getDynamicValue() != null) { - switch (value.getDynamicValue().getSourceType()) { + if (value != null) { + switch (value.getSourceType()) { case CURRENT_DEVICE: - ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); - if (ekv != null || !value.getDynamicValue().isInherit()) { + ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getSourceAttribute())); + if (ekv != null || !value.isInherit()) { break; } case CURRENT_CUSTOMER: - ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute()); - if (ekv != null || !value.getDynamicValue().isInherit()) { + ekv = dynamicPredicateValueCtx.getCustomerValue(value.getSourceAttribute()); + if (ekv != null || !value.isInherit()) { break; } case CURRENT_TENANT: - ekv = dynamicPredicateValueCtx.getTenantValue(value.getDynamicValue().getSourceAttribute()); + ekv = dynamicPredicateValueCtx.getTenantValue(value.getSourceAttribute()); } } return ekv; 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 614cfd4b9c..b0730a5841 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 @@ -80,7 +80,7 @@ class AlarmState { public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException { initCurrentAlarm(ctx); - return createOrClearAlarms(ctx, null, ts, null, AlarmRuleState::eval); + return createOrClearAlarms(ctx, null, ts, null, (alarmState, tsParam) -> alarmState.eval(tsParam, dataSnapshot)); } public boolean createOrClearAlarms(TbContext ctx, TbMsg msg, T data, SnapshotUpdate update, BiFunction evalFunction) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java index 8fbacc120d..0cd4ed63ee 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java @@ -19,24 +19,21 @@ import lombok.AccessLevel; import lombok.Getter; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.alarm.AlarmSeverity; -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.AlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType; import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.query.ComplexFilterPredicate; 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.FilterPredicateValue; -import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; -import org.thingsboard.server.common.data.query.StringFilterPredicate; -import javax.print.attribute.standard.Severity; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -79,6 +76,7 @@ class ProfileState { ruleKeys.add(keyFilter.getKey()); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys); } + addEntityKeysFromAlarmConditionSpec(alarmRule); })); if (alarm.getClearRule() != null) { var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>()); @@ -87,11 +85,43 @@ class ProfileState { clearAlarmKeys.add(keyFilter.getKey()); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys); } + addEntityKeysFromAlarmConditionSpec(alarm.getClearRule()); } } } } + private void addEntityKeysFromAlarmConditionSpec(AlarmRule alarmRule) { + AlarmConditionSpec spec = alarmRule.getCondition().getSpec(); + if (spec == null) { + return; + } + AlarmConditionSpecType specType = spec.getType(); + switch (specType) { + case DURATION: + DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec; + if(duration.getPredicate().getDynamicValue() != null + && duration.getPredicate().getDynamicValue().getSourceAttribute() != null) { + entityKeys.add( + new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, + duration.getPredicate().getDynamicValue().getSourceAttribute()) + ); + } + break; + case REPEATING: + RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec; + if(repeating.getPredicate().getDynamicValue() != null + && repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) { + entityKeys.add( + new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, + repeating.getPredicate().getDynamicValue().getSourceAttribute()) + ); + } + break; + } + + } + private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set entityKeys, Set ruleKeys) { switch (predicate.getType()) { case STRING: 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 faf934cd5e..d3f3280a88 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 @@ -42,6 +42,8 @@ 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.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec; +import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; @@ -64,12 +66,15 @@ import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey; import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.timeseries.TimeseriesService; +import java.math.BigDecimal; +import java.math.RoundingMode; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.TreeMap; import java.util.UUID; +import java.util.concurrent.TimeUnit; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; @@ -94,10 +99,10 @@ public class TbDeviceProfileNodeTest { @Mock private AttributesService attributesService; - private TenantId tenantId = new TenantId(UUID.randomUUID()); - private DeviceId deviceId = new DeviceId(UUID.randomUUID()); - private CustomerId customerId = new CustomerId(UUID.randomUUID()); - private DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID()); + private final TenantId tenantId = new TenantId(UUID.randomUUID()); + private final DeviceId deviceId = new DeviceId(UUID.randomUUID()); + private final CustomerId customerId = new CustomerId(UUID.randomUUID()); + private final DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID()); @Test public void testRandomMessageType() throws Exception { @@ -444,6 +449,642 @@ public class TbDeviceProfileNodeTest { verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); } + @Test + public void testCurrentDeviceAttributeForDynamicDurationValue() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + ); + + AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); + alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + long alarmDelayInSeconds = 5L; + alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds); + alarmDelayAttributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData(); + + ListenableFuture> listListenableFuture = + Futures.immediateFuture(Arrays.asList(entry, alarmDelayAttributeKvEntry)); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false) + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + 10L, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", false) + ); + + DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec(); + durationSpec.setUnit(TimeUnit.SECONDS); + durationSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(durationSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFuture); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + 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", 35); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds) + .multiply(BigDecimal.valueOf(1000)) + .divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN) + .intValueExact(); + Thread.sleep(halfOfAlarmDelay); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + Thread.sleep(halfOfAlarmDelay); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testInheritTenantAttributeForDuration() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + ); + + AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); + alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + long alarmDelayInSeconds = 5L; + alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds); + alarmDelayAttributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData(); + + ListenableFuture> optionalDurationAttribute = + Futures.immediateFuture(Optional.of(alarmDelayAttributeKvEntry)); + ListenableFuture> listNoDurationAttribute = + Futures.immediateFuture(Collections.singletonList(entry)); + ListenableFuture> emptyOptional = + Futures.immediateFuture(Optional.empty()); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false) + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + 10L, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", true) + ); + + DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec(); + durationSpec.setUnit(TimeUnit.SECONDS); + durationSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(durationSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) + .thenReturn(optionalDurationAttribute); + Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) + .thenReturn(device); + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + .thenReturn(emptyOptional); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listNoDurationAttribute); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + 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", 150); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds) + .multiply(BigDecimal.valueOf(1000)) + .divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN) + .intValueExact(); + Thread.sleep(halfOfAlarmDelay); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + Thread.sleep(halfOfAlarmDelay); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testCurrentDeviceAttributeForDynamicRepeatingValue() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + ); + + AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); + alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + long alarmRepeating = 2; + alarmDelayAttributeKvEntity.setLongValue(alarmRepeating); + alarmDelayAttributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData(); + + ListenableFuture> listListenableFuture = + Futures.immediateFuture(Arrays.asList(entry, alarmDelayAttributeKvEntry)); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false) + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + 10, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", false) + ); + + + RepeatingAlarmConditionSpec repeatingSpec = new RepeatingAlarmConditionSpec(); + repeatingSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(repeatingSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFuture); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + 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", 150); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testInheritTenantAttributeForRepeating() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay" + ); + + AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity(); + alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey); + long repeatingCondition = 2; + alarmDelayAttributeKvEntity.setLongValue(repeatingCondition); + alarmDelayAttributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData(); + + ListenableFuture> optionalDurationAttribute = + Futures.immediateFuture(Optional.of(alarmDelayAttributeKvEntry)); + ListenableFuture> listNoDurationAttribute = + Futures.immediateFuture(Collections.singletonList(entry)); + ListenableFuture> emptyOptional = + Futures.immediateFuture(Optional.empty()); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false) + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + 10, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", true) + ); + + RepeatingAlarmConditionSpec repeatingSpec = new RepeatingAlarmConditionSpec(); + repeatingSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(repeatingSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString())) + .thenReturn(optionalDurationAttribute); + Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId)) + .thenReturn(device); + Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString())) + .thenReturn(emptyOptional); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listNoDurationAttribute); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + 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", 150); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testCurrentDeviceAttributeForUseDefaultDurationWhenDynamicDurationValueIsNull() throws Exception { + init(); + + long alarmDelayInSeconds = 5; + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + ListenableFuture> listListenableFuture = + Futures.immediateFuture(Collections.singletonList(entry)); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute") + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + FilterPredicateValue filterPredicateValue = new FilterPredicateValue<>( + alarmDelayInSeconds, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, null, false) + ); + + DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec(); + durationSpec.setUnit(TimeUnit.SECONDS); + durationSpec.setPredicate(filterPredicateValue); + alarmCondition.setSpec(durationSpec); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFuture); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + 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", 35); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds) + .multiply(BigDecimal.valueOf(1000)) + .divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN) + .intValueExact(); + Thread.sleep(halfOfAlarmDelay); + + verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created"); + + Thread.sleep(halfOfAlarmDelay); + + TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg2); + verify(ctx).tellSuccess(msg2); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test + public void testCurrentDeviceAttributeForUseDefaultRepeatingWhenDynamicDurationValueIsNull() throws Exception { + init(); + + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setId(deviceProfileId); + DeviceProfileData deviceProfileData = new DeviceProfileData(); + + Device device = new Device(); + device.setId(deviceId); + device.setCustomerId(customerId); + + AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey( + EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute" + ); + + AttributeKvEntity attributeKvEntity = new AttributeKvEntity(); + attributeKvEntity.setId(compositeKey); + attributeKvEntity.setLongValue(30L); + attributeKvEntity.setLastUpdateTs(0L); + + AttributeKvEntry entry = attributeKvEntity.toData(); + + ListenableFuture> listListenableFuture = + Futures.immediateFuture(Collections.singletonList(entry)); + + AlarmConditionFilter highTempFilter = new AlarmConditionFilter(); + highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature")); + highTempFilter.setValueType(EntityKeyValueType.NUMERIC); + NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate(); + highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + highTemperaturePredicate.setValue(new FilterPredicateValue<>( + 0.0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute") + )); + highTempFilter.setPredicate(highTemperaturePredicate); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setCondition(Collections.singletonList(highTempFilter)); + + RepeatingAlarmConditionSpec repeating = new RepeatingAlarmConditionSpec(); + repeating.setPredicate(new FilterPredicateValue<>( + 0, + null, + new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_rule", false) + )); + alarmCondition.setSpec(repeating); + + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setCondition(alarmCondition); + DeviceProfileAlarm dpa = new DeviceProfileAlarm(); + dpa.setId("highTemperatureAlarmID"); + dpa.setAlarmType("highTemperatureAlarm"); + dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule))); + + deviceProfileData.setAlarms(Collections.singletonList(dpa)); + deviceProfile.setProfileData(deviceProfileData); + + Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile); + Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature"))) + .thenReturn(Futures.immediateFuture(Collections.emptyList())); + Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm")) + .thenReturn(Futures.immediateFuture(null)); + Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg()); + Mockito.when(ctx.getAttributesService()).thenReturn(attributesService); + Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet())) + .thenReturn(listListenableFuture); + + TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), ""); + 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", 35); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(), + TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null); + + node.onMsg(ctx, msg); + verify(ctx).tellSuccess(msg); + verify(ctx).tellNext(theMsg, "Alarm Created"); + verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); + } + + @Test public void testCurrentCustomersAttributeForDynamicValue() throws Exception { init(); diff --git a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts index 934a4774fb..998b329dc9 100644 --- a/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts +++ b/ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts @@ -30,7 +30,9 @@ import { DynamicValueSourceType, dynamicValueSourceTypeTranslationMap, EntityKeyValueType, - FilterPredicateValue + FilterPredicateValue, + getDynamicSourcesForAllowUser, + inheritModeForDynamicValueSourceType } from '@shared/models/query/query.models'; @Component({ @@ -52,22 +54,14 @@ import { }) export class FilterPredicateValueComponent implements ControlValueAccessor, Validator, OnInit { - private readonly inheritModeForSources: DynamicValueSourceType[] = [ - DynamicValueSourceType.CURRENT_CUSTOMER, - DynamicValueSourceType.CURRENT_DEVICE]; + private readonly inheritModeForSources: DynamicValueSourceType[] = inheritModeForDynamicValueSourceType; @Input() disabled: boolean; @Input() set allowUserDynamicSource(allow: boolean) { - this.dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_TENANT, - DynamicValueSourceType.CURRENT_CUSTOMER]; + this.dynamicValueSourceTypes = getDynamicSourcesForAllowUser(allow); this.allow = allow; - if (allow) { - this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER); - } else { - this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE); - } } private onlyUserDynamicSourceValue = false; @@ -92,8 +86,9 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali valueTypeEnum = EntityKeyValueType; - dynamicValueSourceTypes: DynamicValueSourceType[] = [DynamicValueSourceType.CURRENT_TENANT, - DynamicValueSourceType.CURRENT_CUSTOMER, DynamicValueSourceType.CURRENT_USER]; + allow = true; + + dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(this.allow); dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; @@ -103,8 +98,6 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali inheritMode = false; - allow = true; - private propagateChange = null; private propagateChangePending = false; 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 d0ad42b62e..1ea914f939 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,7 @@ 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 { AlarmDurationPredicateValueComponent } from '@home/components/profile/alarm/alarm-duration-predicate-value.component'; import { SecurityConfigLwm2mComponent } from '@home/components/device/security-config-lwm2m.component'; import { SecurityConfigLwm2mServerComponent } from '@home/components/device/security-config-lwm2m-server.component'; import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component'; @@ -242,6 +243,7 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic AlarmScheduleInfoComponent, DeviceProfileProvisionConfigurationComponent, AlarmScheduleComponent, + AlarmDurationPredicateValueComponent, DeviceWizardDialogComponent, DeviceCredentialsComponent, CopyDeviceCredentialsComponent, @@ -358,6 +360,7 @@ import { SnmpDeviceProfileTransportModule } from '@home/components/profile/devic AlarmScheduleInfoComponent, AlarmScheduleComponent, AlarmScheduleDialogComponent, + AlarmDurationPredicateValueComponent, EditAlarmDetailsDialogComponent, DeviceProfileProvisionConfigurationComponent, AlarmScheduleComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html new file mode 100644 index 0000000000..80841767e0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html @@ -0,0 +1,79 @@ + +

+
+ + + + + {{ defaultValueRequiredError | translate }} + + + {{ defaultValueRangeError | translate }} + + + {{ defaultValueRangeError | translate }} + + + {{ defaultValuePatternError | translate }} + + +
+
+
+
+ + + + + {{'filter.no-dynamic-value' | translate}} + + + {{dynamicValueSourceTypeTranslations.get(sourceType) | translate}} + + + +
+
+ + + + +
+
+ + {{ 'filter.inherit-owner' | translate}} + +
+
+
+ +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss new file mode 100644 index 0000000000..7e140d8e90 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss @@ -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. + */ + +:host ::ng-deep { + .source-attribute { + .mat-form-field-infix{ + width: 100%; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts new file mode 100644 index 0000000000..a0a0a0fbab --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts @@ -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. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { + DynamicValueSourceType, + dynamicValueSourceTypeTranslationMap, + FilterPredicateValue, + getDynamicSourcesForAllowUser, + inheritModeForDynamicValueSourceType +} from '@shared/models/query/query.models'; +import { AlarmConditionType } from '@shared/models/device.models'; + +@Component({ + selector: 'tb-alarm-duration-predicate-value', + templateUrl: './alarm-duration-predicate-value.component.html', + styleUrls: ['./alarm-duration-predicate-value.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => AlarmDurationPredicateValueComponent), + multi: true + } + ] +}) +export class AlarmDurationPredicateValueComponent implements ControlValueAccessor, OnInit { + + private readonly inheritModeForSources = inheritModeForDynamicValueSourceType; + + @Input() + set alarmConditionType(alarmConditionType: AlarmConditionType) { + switch (alarmConditionType) { + case AlarmConditionType.REPEATING: + this.defaultValuePlaceholder = 'device-profile.condition-repeating-value-required'; + this.defaultValueRequiredError = 'device-profile.condition-repeating-value-range'; + this.defaultValueRangeError = 'device-profile.condition-repeating-value-range'; + this.defaultValuePatternError = 'device-profile.condition-repeating-value-pattern'; + break; + case AlarmConditionType.DURATION: + this.defaultValuePlaceholder = 'device-profile.condition-duration-value'; + this.defaultValueRequiredError = 'device-profile.condition-duration-value-required'; + this.defaultValueRangeError = 'device-profile.condition-duration-value-range'; + this.defaultValuePatternError = 'device-profile.condition-duration-value-pattern'; + break; + } + } + + defaultValuePlaceholder = ''; + defaultValueRequiredError = ''; + defaultValueRangeError = ''; + defaultValuePatternError = ''; + + dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(false); + + dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; + + alarmDurationPredicateValueFormGroup: FormGroup; + + dynamicMode = false; + + inheritMode = false; + + private propagateChange = null; + + constructor(private fb: FormBuilder) { + } + + ngOnInit(): void { + this.alarmDurationPredicateValueFormGroup = this.fb.group({ + defaultValue: [0, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]], + dynamicValue: this.fb.group( + { + sourceType: [null], + sourceAttribute: [null], + inherit: [false] + } + ) + }); + this.alarmDurationPredicateValueFormGroup.get('dynamicValue').get('sourceType').valueChanges.subscribe( + (sourceType) => { + if (!sourceType) { + this.alarmDurationPredicateValueFormGroup.get('dynamicValue').get('sourceAttribute').patchValue(null, {emitEvent: false}); + } + this.updateShowInheritMode(sourceType); + } + ); + this.alarmDurationPredicateValueFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + if (isDisabled) { + this.alarmDurationPredicateValueFormGroup.disable({emitEvent: false}); + } else { + this.alarmDurationPredicateValueFormGroup.enable({emitEvent: false}); + } + } + + writeValue(predicateValue: FilterPredicateValue): void { + this.alarmDurationPredicateValueFormGroup.patchValue({ + defaultValue: predicateValue ? predicateValue.defaultValue : null, + dynamicValue: { + sourceType: predicateValue?.dynamicValue ? predicateValue.dynamicValue.sourceType : null, + sourceAttribute: predicateValue?.dynamicValue ? predicateValue.dynamicValue.sourceAttribute : null, + inherit: predicateValue?.dynamicValue ? predicateValue.dynamicValue.inherit : null + } + }, {emitEvent: false}); + } + + private updateModel() { + let predicateValue: FilterPredicateValue = null; + if (this.alarmDurationPredicateValueFormGroup.valid) { + predicateValue = this.alarmDurationPredicateValueFormGroup.getRawValue(); + if (predicateValue.dynamicValue) { + if (!predicateValue.dynamicValue.sourceType || !predicateValue.dynamicValue.sourceAttribute) { + predicateValue.dynamicValue = null; + } + } + } + this.propagateChange(predicateValue); + } + + private updateShowInheritMode(sourceType: DynamicValueSourceType) { + if (this.inheritModeForSources.includes(sourceType)) { + this.inheritMode = true; + } else { + this.alarmDurationPredicateValueFormGroup.get('dynamicValue.inherit').patchValue(false, {emitEvent: false}); + this.inheritMode = false; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html index 2944d565cd..bc07daddbb 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html @@ -37,7 +37,7 @@ [entityId]="entityId" formControlName="keyFilters"> -
+
device-profile.condition-type @@ -49,60 +49,26 @@ {{ 'device-profile.condition-type-required' | translate }} -
- - - - - {{ 'device-profile.condition-duration-value-required' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-range' | translate }} - - - {{ 'device-profile.condition-duration-value-pattern' | translate }} - - - - - - - {{ timeUnitTranslations.get(timeUnit) | translate }} - - - - {{ 'device-profile.condition-duration-time-unit-required' | translate }} - - -
-
- - - - - {{ 'device-profile.condition-repeating-value-required' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-range' | translate }} - - - {{ 'device-profile.condition-repeating-value-pattern' | translate }} - - +
+ + +
+ + + + + {{ timeUnitTranslations.get(timeUnit) | translate }} + + + + {{ 'device-profile.condition-duration-time-unit-required' | translate }} + + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts index a131e9748a..c11d63da2e 100644 --- a/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts @@ -43,12 +43,11 @@ export interface AlarmRuleConditionDialogData { export class AlarmRuleConditionDialogComponent extends DialogComponent implements OnInit, ErrorStateMatcher { - timeUnits = Object.keys(TimeUnit); + timeUnits = Object.values(TimeUnit); timeUnitTranslations = timeUnitTranslationMap; - alarmConditionTypes = Object.keys(AlarmConditionType); + alarmConditionTypes = Object.values(AlarmConditionType); AlarmConditionType = AlarmConditionType; alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap; - readonly = this.data.readonly; condition = this.data.condition; entityId = this.data.entityId; @@ -70,9 +69,8 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent; } export interface AlarmCondition { diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index dbe114e18e..e504a3480d 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -202,6 +202,17 @@ export function createDefaultFilterPredicate(valueType: EntityKeyValueType, comp return predicate; } +export function getDynamicSourcesForAllowUser(allow: boolean): DynamicValueSourceType[] { + const dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_TENANT, + DynamicValueSourceType.CURRENT_CUSTOMER]; + if (allow) { + dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER); + } else { + dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE); + } + return dynamicValueSourceTypes; +} + export enum FilterPredicateType { STRING = 'STRING', NUMERIC = 'NUMERIC', @@ -289,6 +300,10 @@ export const dynamicValueSourceTypeTranslationMap = new Map { sourceType: DynamicValueSourceType; sourceAttribute: 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 8a78b21571..0536cf328f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1179,6 +1179,7 @@ "condition-type-simple": "Simple", "condition-type-duration": "Duration", "condition-during": "During {{during}}", + "condition-during-dynamic": "During \"{{ attribute }}\" ({{during}})", "condition-type-repeating": "Repeating", "condition-type-required": "Condition type is required.", "condition-repeating-value": "Count of events", @@ -1186,6 +1187,7 @@ "condition-repeating-value-pattern": "Count of events should be integers.", "condition-repeating-value-required": "Count of events is required.", "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", + "condition-repeat-times-dynamic": "Repeats \"{ attribute }\" ({ count, plural, 1 {1 time} other {# times} })", "schedule-type": "Scheduler type", "schedule-type-required": "Scheduler type is required.", "schedule": "Schedule", From 228fddb8cd92bf4ec89f16760aef7f190a45bb96 Mon Sep 17 00:00:00 2001 From: AndrewVolosytnykhThingsboard <77969531+AndrewVolosytnykhThingsboard@users.noreply.github.com> Date: Tue, 6 Jul 2021 17:24:35 +0300 Subject: [PATCH 231/231] [PROD-678] Authorization and password reset vulnerability fix (#4569) * Fixed vulnerabilities for password reset and authorization * Improvements to check user and credentials for null * Correct messages and logs * Improvements * Reset Password Test: added delay after resetting password to synchronize test with server * Executor removed from controller * Correct method calling * Formatting cleaned --- .../server/controller/AuthController.java | 10 +++++----- .../exception/ThingsboardErrorResponseHandler.java | 3 ++- .../server/service/mail/DefaultMailService.java | 14 ++++++++++++++ .../server/controller/BaseUserControllerTest.java | 1 + .../server/service/mail/TestMailService.java | 2 +- .../server/dao/user/UserServiceImpl.java | 11 +++++++---- .../thingsboard/rule/engine/api/MailService.java | 13 ++++++++----- .../login/reset-password-request.component.html | 5 +++-- .../login/reset-password-request.component.ts | 8 ++++++++ .../src/assets/locale/locale.constant-en_US.json | 2 +- 10 files changed, 50 insertions(+), 19 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index f41e40ef89..3c4db474ef 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -135,7 +135,7 @@ public class AuthController extends BaseController { } } - @RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET) + @RequestMapping(value = "/noauth/activate", params = {"activateToken"}, method = RequestMethod.GET) public ResponseEntity checkActivateToken( @RequestParam(value = "activateToken") String activateToken) { HttpHeaders headers = new HttpHeaders(); @@ -159,7 +159,7 @@ public class AuthController extends BaseController { @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) - public void requestResetPasswordByEmail ( + public void requestResetPasswordByEmail( @RequestBody JsonNode resetPasswordByEmailRequest, HttpServletRequest request) throws ThingsboardException { try { @@ -170,13 +170,13 @@ public class AuthController extends BaseController { String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, userCredentials.getResetToken()); - mailService.sendResetPasswordEmail(resetUrl, email); + mailService.sendResetPasswordEmailAsync(resetUrl, email); } catch (Exception e) { - throw handleException(e); + log.warn("Error occurred: {}", e.getMessage()); } } - @RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET) + @RequestMapping(value = "/noauth/resetPassword", params = {"resetToken"}, method = RequestMethod.GET) public ResponseEntity checkResetToken( @RequestParam(value = "resetToken") String resetToken) { HttpHeaders headers = new HttpHeaders(); diff --git a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java index 28f0c38eee..906e87d608 100644 --- a/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java +++ b/application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java @@ -25,6 +25,7 @@ import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -152,7 +153,7 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException { response.setStatus(HttpStatus.UNAUTHORIZED.value()); - if (authenticationException instanceof BadCredentialsException) { + if (authenticationException instanceof BadCredentialsException || authenticationException instanceof UsernameNotFoundException) { mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); } else if (authenticationException instanceof DisabledException) { mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); 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 715c99e2fb..5ef7cc1942 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 @@ -73,6 +73,9 @@ public class DefaultMailService implements MailService { @Autowired private TbApiUsageStateService apiUsageStateService; + @Autowired + private MailExecutorService mailExecutorService; + private JavaMailSenderImpl mailSender; private String mailFrom; @@ -221,6 +224,17 @@ public class DefaultMailService implements MailService { sendMail(mailSender, mailFrom, email, subject, message); } + @Override + public void sendResetPasswordEmailAsync(String passwordResetLink, String email) { + mailExecutorService.execute(() -> { + try { + this.sendResetPasswordEmail(passwordResetLink, email); + } catch (ThingsboardException e) { + log.error("Error occurred: {} ", e.getMessage()); + } + }); + } + @Override public void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 943852125c..600b9656f2 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -155,6 +155,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest) .andExpect(status().isOk()); + Thread.sleep(1000); doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) .andExpect(status().isSeeOther()) .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); diff --git a/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java b/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java index d2e9dddbf2..b68319fe73 100644 --- a/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java +++ b/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java @@ -51,7 +51,7 @@ public class TestMailService { currentResetPasswordToken = passwordResetLink.split("=")[1]; return null; } - }).when(mailService).sendResetPasswordEmail(Mockito.anyString(), Mockito.anyString()); + }).when(mailService).sendResetPasswordEmailAsync(Mockito.anyString(), Mockito.anyString()); return mailService; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index ada6208b6b..f406fd2a69 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -25,7 +25,10 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; +import org.springframework.security.authentication.DisabledException; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; @@ -49,7 +52,6 @@ import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; -import org.thingsboard.common.util.JacksonUtil; import java.util.HashMap; import java.util.Map; @@ -194,11 +196,11 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic DataValidator.validateEmail(email); User user = userDao.findByEmail(tenantId, email); if (user == null) { - throw new IncorrectParameterException(String.format("Unable to find user by email [%s]", email)); + throw new UsernameNotFoundException(String.format("Unable to find user by email [%s]", email)); } UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId()); if (!userCredentials.isEnabled()) { - throw new IncorrectParameterException("Unable to reset password for inactive user"); + throw new DisabledException(String.format("User credentials not enabled [%s]", email)); } userCredentials.setResetToken(RandomStringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); return saveUserCredentials(tenantId, userCredentials); @@ -365,7 +367,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic JsonNode userPasswordHistoryJson; if (additionalInfo.has(USER_PASSWORD_HISTORY)) { userPasswordHistoryJson = additionalInfo.get(USER_PASSWORD_HISTORY); - userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>(){}); + userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() { + }); } if (userPasswordHistoryMap != null) { userPasswordHistoryMap.put(Long.toString(System.currentTimeMillis()), userCredentials.getPassword()); 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 bd75d8bded..0311a5b26f 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 @@ -31,22 +31,25 @@ public interface MailService { void updateMailConfiguration(); void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException; - + void sendTestMail(JsonNode config, String email) throws ThingsboardException; - + void sendActivationEmail(String activationLink, String email) throws ThingsboardException; - + void sendAccountActivatedEmail(String loginLink, String email) throws ThingsboardException; - + void sendResetPasswordEmail(String passwordResetLink, String email) throws ThingsboardException; + void sendResetPasswordEmailAsync(String passwordResetLink, String email); + void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; - void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; + void sendAccountLockoutEmail(String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; void send(TenantId tenantId, CustomerId customerId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images) throws ThingsboardException; void send(TenantId tenantId, CustomerId customerId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map images, JavaMailSender javaMailSender) throws ThingsboardException; void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; + } diff --git a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html index 8e723d15a4..72e263a802 100644 --- a/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html @@ -15,7 +15,8 @@ limitations under the License. --> -
+
login.request-password-reset @@ -38,7 +39,7 @@
-