Browse Source

Merge branch 'master' into removed-version-from-compose

pull/13722/head
Yatharth Sharma 1 year ago
committed by GitHub
parent
commit
d24cc2cec5
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 61
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  2. 183
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  3. 22
      application/src/main/java/org/thingsboard/server/service/mail/TbMailSender.java
  4. 3
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java
  5. 2
      application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java
  6. 16
      application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java
  7. 12
      application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java
  8. 316
      application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java
  9. 97
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  10. 4
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java
  11. 26
      application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java
  12. 31
      application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDataReceivedFromClientTest.java
  13. 5
      common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java
  14. 3
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  15. 6
      common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java
  16. 18
      common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java
  17. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java
  18. 4
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java
  19. 190
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  20. 4
      common/data/src/main/java/org/thingsboard/server/common/data/id/JobId.java
  21. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java
  22. 12
      common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java
  23. 1
      common/proto/src/main/proto/queue.proto
  24. 21
      common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java
  25. 68
      common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java
  26. 2
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java
  27. 10
      dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java
  28. 14
      dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java
  29. 22
      dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java
  30. 3
      dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java
  31. 34
      dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java
  32. 8
      dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java
  33. 7
      netty-mqtt/src/main/java/org/thingsboard/mqtt/ChannelClosedException.java
  34. 4
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientCallback.java
  35. 124
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java
  36. 77
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java
  37. 16
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttConnectResult.java
  38. 30
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttSubscription.java
  39. 2
      pom.xml
  40. 47
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java
  41. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java
  42. 49
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeTest.java
  43. 8
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java

61
application/src/main/java/org/thingsboard/server/controller/AdminController.java

@ -37,14 +37,13 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
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.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
@ -125,14 +124,13 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)",
notes = "Get the Administration Settings object using specified string key. Referencing non-existing key will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/{key}", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/settings/{key}")
public AdminSettings getAdminSettings(
@Parameter(description = "A string value of the key (e.g. 'general' or 'mail').")
@PathVariable("key") String key) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key), "No Administration settings found for key: " + key);
if (adminSettings.getKey().equals("mail")) {
if (adminSettings.getKey().equals(MAIL_SETTINGS_KEY)) {
((ObjectNode) adminSettings.getJsonValue()).remove("password");
((ObjectNode) adminSettings.getJsonValue()).remove("refreshToken");
}
@ -144,15 +142,14 @@ public class AdminController extends BaseController {
"The Administration Settings Id will be present in the response. Specify the Administration Settings Id when you would like to update the Administration Settings. " +
"Referencing non-existing Administration Settings Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings", method = RequestMethod.POST)
@ResponseBody
@PostMapping(value = "/settings")
public AdminSettings saveAdminSettings(
@Parameter(description = "A JSON value representing the Administration Settings.")
@RequestBody AdminSettings adminSettings) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE);
adminSettings.setTenantId(getTenantId());
adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings));
if (adminSettings.getKey().equals("mail")) {
if (adminSettings.getKey().equals(MAIL_SETTINGS_KEY)) {
mailService.updateMailConfiguration();
((ObjectNode) adminSettings.getJsonValue()).remove("password");
((ObjectNode) adminSettings.getJsonValue()).remove("refreshToken");
@ -165,8 +162,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Get the Security Settings object (getSecuritySettings)",
notes = "Get the Security Settings object that contains password policy, etc." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/securitySettings", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/securitySettings")
public SecuritySettings getSecuritySettings() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return checkNotNull(securitySettingsService.getSecuritySettings());
@ -175,8 +171,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Update Security Settings (saveSecuritySettings)",
notes = "Updates the Security Settings object that contains password policy, etc." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/securitySettings", method = RequestMethod.POST)
@ResponseBody
@PostMapping(value = "/securitySettings")
public SecuritySettings saveSecuritySettings(
@Parameter(description = "A JSON value representing the Security Settings.")
@RequestBody SecuritySettings securitySettings) throws ThingsboardException {
@ -188,8 +183,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Get the JWT Settings object (getJwtSettings)",
notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/jwtSettings")
public JwtSettings getJwtSettings() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return checkNotNull(jwtSettingsService.getJwtSettings());
@ -198,8 +192,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Update JWT Settings (saveJwtSettings)",
notes = "Updates the JWT Settings object that contains JWT token policy, etc. The tokenSigningKey field is a Base64 encoded string." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.POST)
@ResponseBody
@PostMapping(value = "/jwtSettings")
public JwtPair saveJwtSettings(
@Parameter(description = "A JSON value representing the JWT Settings.")
@RequestBody JwtSettings jwtSettings) throws ThingsboardException {
@ -213,15 +206,15 @@ public class AdminController extends BaseController {
notes = "Attempts to send test email to the System Administrator User using Mail Settings provided as a parameter. " +
"You may change the 'To' email in the user profile of the System Administrator. " + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/testMail", method = RequestMethod.POST)
@PostMapping(value = "/settings/testMail")
public void sendTestMail(
@Parameter(description = "A JSON value representing the Mail Settings.")
@RequestBody AdminSettings adminSettings) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
adminSettings = checkNotNull(adminSettings);
if (adminSettings.getKey().equals("mail")) {
if (adminSettings.getKey().equals(MAIL_SETTINGS_KEY)) {
if (adminSettings.getJsonValue().has("enableOauth2") && adminSettings.getJsonValue().get("enableOauth2").asBoolean()) {
AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"));
AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, MAIL_SETTINGS_KEY));
JsonNode refreshToken = mailSettings.getJsonValue().get("refreshToken");
if (refreshToken == null) {
throw new ThingsboardException("Refresh token was not generated. Please, generate refresh token.", ThingsboardErrorCode.GENERAL);
@ -230,7 +223,7 @@ public class AdminController extends BaseController {
settings.put("refreshToken", refreshToken.asText());
} else {
if (!adminSettings.getJsonValue().has("password")) {
AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"));
AdminSettings mailSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, MAIL_SETTINGS_KEY));
((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText());
}
}
@ -251,7 +244,7 @@ public class AdminController extends BaseController {
notes = "Attempts to send test sms to the System Administrator User using SMS Settings and phone number provided as a parameters of the request. "
+ SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/testSms", method = RequestMethod.POST)
@PostMapping(value = "/settings/testSms")
public void sendTestSms(
@Parameter(description = "A JSON value representing the Test SMS request.")
@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException {
@ -325,7 +318,7 @@ public class AdminController extends BaseController {
notes = "Deletes the repository settings."
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/repositorySettings", method = RequestMethod.DELETE)
@DeleteMapping(value = "/repositorySettings")
@ResponseStatus(value = HttpStatus.OK)
public DeferredResult<Void> deleteRepositorySettings() throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE);
@ -335,7 +328,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Check repository access (checkRepositoryAccess)",
notes = "Attempts to check repository access. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/repositorySettings/checkAccess", method = RequestMethod.POST)
@PostMapping(value = "/repositorySettings/checkAccess")
public DeferredResult<Void> checkRepositoryAccess(
@Parameter(description = "A JSON value representing the Repository Settings.")
@RequestBody RepositorySettings settings) throws Exception {
@ -376,7 +369,7 @@ public class AdminController extends BaseController {
notes = "Deletes the auto commit settings."
+ TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/autoCommitSettings", method = RequestMethod.DELETE)
@DeleteMapping(value = "/autoCommitSettings")
@ResponseStatus(value = HttpStatus.OK)
public void deleteAutoCommitSettings() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE);
@ -387,9 +380,8 @@ public class AdminController extends BaseController {
notes = "Check notifications about new platform releases. "
+ SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/updates", method = RequestMethod.GET)
@ResponseBody
public UpdateMessage checkUpdates() throws ThingsboardException {
@GetMapping(value = "/updates")
public UpdateMessage checkUpdates() {
return updateService.checkUpdates();
}
@ -397,9 +389,8 @@ public class AdminController extends BaseController {
notes = "Get main information about system. "
+ SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/systemInfo", method = RequestMethod.GET)
@ResponseBody
public SystemInfo getSystemInfo() throws ThingsboardException {
@GetMapping(value = "/systemInfo")
public SystemInfo getSystemInfo() {
return systemInfoService.getSystemInfo();
}
@ -407,8 +398,7 @@ public class AdminController extends BaseController {
notes = "Get information about enabled/disabled features. "
+ SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/featuresInfo", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/featuresInfo")
public FeaturesInfo getFeaturesInfo() {
return systemInfoService.getFeaturesInfo();
}
@ -417,8 +407,7 @@ public class AdminController extends BaseController {
"double quotes. After successful authentication with OAuth2 provider and user consent for requested scope, it makes a redirect to this path so that the platform can do " +
"further log in processing and generating access tokens. " + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/mail/oauth2/loginProcessingUrl", method = RequestMethod.GET)
@ResponseBody
@GetMapping(value = "/mail/oauth2/loginProcessingUrl")
public String getMailProcessingUrl() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return "\"/api/admin/mail/oauth2/code\"";
@ -427,7 +416,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Redirect user to mail provider login page. ", notes = "After user logged in and provided access" +
"provider sends authorization code to specified redirect uri.)")
@PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/mail/oauth2/authorize", method = RequestMethod.GET, produces = "application/text")
@GetMapping(value = "/mail/oauth2/authorize", produces = "application/text")
public String getAuthorizationUrl(HttpServletRequest request, HttpServletResponse response) throws ThingsboardException {
String state = StringUtils.generateSafeToken();
if (request.getParameter(PREV_URI_PATH_PARAMETER) != null) {
@ -452,7 +441,7 @@ public class AdminController extends BaseController {
.build() + "\"";
}
@RequestMapping(value = "/mail/oauth2/code", params = {"code", "state"}, method = RequestMethod.GET)
@GetMapping(value = "/mail/oauth2/code", params = {"code", "state"})
public void codeProcessingUrl(
@RequestParam(value = "code") String code, @RequestParam(value = "state") String state,
HttpServletRequest request, HttpServletResponse response) throws ThingsboardException, IOException {

183
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -22,9 +22,9 @@ import freemarker.template.Template;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Lazy;
@ -64,55 +64,37 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@Service
@Slf4j
@Service
@RequiredArgsConstructor
public class DefaultMailService implements MailService {
public static final String TARGET_EMAIL = "targetEmail";
public static final String UTF_8 = "UTF-8";
private static final String TARGET_EMAIL = "targetEmail";
private static final String UTF_8 = "UTF-8";
private static final long DEFAULT_TIMEOUT = 10_000;
private final ScheduledExecutorService timeoutScheduler = ThingsBoardExecutors.newSingleThreadScheduledExecutor("mail-service-watchdog");
private final MessageSource messages;
private final Configuration freemarkerConfig;
private final AdminSettingsService adminSettingsService;
private final TbApiUsageReportClient apiUsageClient;
private static final long DEFAULT_TIMEOUT = 10_000;
@Lazy
@Autowired
private TbApiUsageStateService apiUsageStateService;
@Autowired
private MailSenderInternalExecutorService mailExecutorService;
@Autowired
private PasswordResetExecutorService passwordResetExecutorService;
@Autowired
private TbMailContextComponent ctx;
@Autowired
private RateLimitService rateLimitService;
private final TbApiUsageStateService apiUsageStateService;
private final MailSenderInternalExecutorService mailExecutorService;
private final PasswordResetExecutorService passwordResetExecutorService;
private final TbMailContextComponent ctx;
private final RateLimitService rateLimitService;
@Value("${mail.per_tenant_rate_limits:}")
private String perTenantRateLimitConfig;
private final ScheduledExecutorService timeoutScheduler;
private TbMailSender mailSender;
private String mailFrom;
private long timeout;
public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageReportClient apiUsageClient) {
this.messages = messages;
this.freemarkerConfig = freemarkerConfig;
this.adminSettingsService = adminSettingsService;
this.apiUsageClient = apiUsageClient;
this.timeoutScheduler = ThingsBoardExecutors.newSingleThreadScheduledExecutor("mail-service-watchdog");
}
@PostConstruct
private void init() {
updateMailConfiguration();
@ -120,9 +102,7 @@ public class DefaultMailService implements MailService {
@PreDestroy
public void destroy() {
if (timeoutScheduler != null) {
timeoutScheduler.shutdownNow();
}
timeoutScheduler.shutdownNow();
}
@Override
@ -311,22 +291,21 @@ public class DefaultMailService implements MailService {
model.put("apiFeature", apiFeature.getLabel());
model.put(TARGET_EMAIL, email);
String message = null;
switch (stateValue) {
case ENABLED:
String message = switch (stateValue) {
case ENABLED -> {
model.put("apiLabel", toEnabledValueLabel(apiFeature));
message = mergeTemplateIntoString("state.enabled.ftl", model);
break;
case WARNING:
yield mergeTemplateIntoString("state.enabled.ftl", model);
}
case WARNING -> {
model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(recordState));
message = mergeTemplateIntoString("state.warning.ftl", model);
break;
case DISABLED:
yield mergeTemplateIntoString("state.warning.ftl", model);
}
case DISABLED -> {
model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(recordState));
message = mergeTemplateIntoString("state.disabled.ftl", model);
break;
}
yield mergeTemplateIntoString("state.disabled.ftl", model);
}
};
sendMail(mailSender, mailFrom, email, subject, message, timeout);
}
@ -341,89 +320,55 @@ public class DefaultMailService implements MailService {
}
private String toEnabledValueLabel(ApiFeature apiFeature) {
switch (apiFeature) {
case DB:
return "save";
case TRANSPORT:
return "receive";
case JS:
return "invoke";
case RE:
return "process";
case EMAIL:
case SMS:
return "send";
case ALARM:
return "create";
default:
throw new RuntimeException("Not implemented!");
}
return switch (apiFeature) {
case DB -> "save";
case TRANSPORT -> "receive";
case JS -> "invoke";
case RE -> "process";
case EMAIL, SMS -> "send";
case ALARM -> "create";
default -> throw new RuntimeException("Not implemented!");
};
}
private String toDisabledValueLabel(ApiFeature apiFeature) {
switch (apiFeature) {
case DB:
return "saved";
case TRANSPORT:
return "received";
case JS:
return "invoked";
case RE:
return "processed";
case EMAIL:
case SMS:
return "sent";
case ALARM:
return "created";
default:
throw new RuntimeException("Not implemented!");
}
return switch (apiFeature) {
case DB -> "saved";
case TRANSPORT -> "received";
case JS -> "invoked";
case RE -> "processed";
case EMAIL, SMS -> "sent";
case ALARM -> "created";
default -> throw new RuntimeException("Not implemented!");
};
}
private String toWarningValueLabel(ApiUsageRecordState recordState) {
String valueInM = recordState.getValueAsString();
String thresholdInM = recordState.getThresholdAsString();
switch (recordState.getKey()) {
case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT:
return valueInM + " out of " + thresholdInM + " allowed data points";
case TRANSPORT_MSG_COUNT:
return valueInM + " out of " + thresholdInM + " allowed messages";
case JS_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed JavaScript functions";
case TBEL_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed Tbel functions";
case RE_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed Rule Engine messages";
case EMAIL_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed Email messages";
case SMS_EXEC_COUNT:
return valueInM + " out of " + thresholdInM + " allowed SMS messages";
default:
throw new RuntimeException("Not implemented!");
}
return switch (recordState.getKey()) {
case STORAGE_DP_COUNT, TRANSPORT_DP_COUNT -> valueInM + " out of " + thresholdInM + " allowed data points";
case TRANSPORT_MSG_COUNT -> valueInM + " out of " + thresholdInM + " allowed messages";
case JS_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed JavaScript functions";
case TBEL_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed Tbel functions";
case RE_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed Rule Engine messages";
case EMAIL_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed Email messages";
case SMS_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed SMS messages";
default -> throw new RuntimeException("Not implemented!");
};
}
private String toDisabledValueLabel(ApiUsageRecordState recordState) {
switch (recordState.getKey()) {
case STORAGE_DP_COUNT:
case TRANSPORT_DP_COUNT:
return recordState.getValueAsString() + " data points";
case TRANSPORT_MSG_COUNT:
return recordState.getValueAsString() + " messages";
case JS_EXEC_COUNT:
return "JavaScript functions " + recordState.getValueAsString() + " times";
case TBEL_EXEC_COUNT:
return "TBEL functions " + recordState.getValueAsString() + " times";
case RE_EXEC_COUNT:
return recordState.getValueAsString() + " Rule Engine messages";
case EMAIL_EXEC_COUNT:
return recordState.getValueAsString() + " Email messages";
case SMS_EXEC_COUNT:
return recordState.getValueAsString() + " SMS messages";
default:
throw new RuntimeException("Not implemented!");
}
return switch (recordState.getKey()) {
case STORAGE_DP_COUNT, TRANSPORT_DP_COUNT -> recordState.getValueAsString() + " data points";
case TRANSPORT_MSG_COUNT -> recordState.getValueAsString() + " messages";
case JS_EXEC_COUNT -> "JavaScript functions " + recordState.getValueAsString() + " times";
case TBEL_EXEC_COUNT -> "TBEL functions " + recordState.getValueAsString() + " times";
case RE_EXEC_COUNT -> recordState.getValueAsString() + " Rule Engine messages";
case EMAIL_EXEC_COUNT -> recordState.getValueAsString() + " Email messages";
case SMS_EXEC_COUNT -> recordState.getValueAsString() + " SMS messages";
default -> throw new RuntimeException("Not implemented!");
};
}
private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email,

22
application/src/main/java/org/thingsboard/server/service/mail/TbMailSender.java

@ -25,6 +25,7 @@ import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.gson.GsonFactory;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable;
import org.springframework.mail.MailException;
@ -50,8 +51,10 @@ public class TbMailSender extends JavaMailSenderImpl {
private final TbMailContextComponent ctx;
private final Lock lock;
@Getter
private final Boolean oauth2Enabled;
private volatile String accessToken;
@Getter
private volatile long tokenExpires;
public TbMailSender(TbMailContextComponent ctx, JsonNode jsonConfig) {
@ -70,14 +73,6 @@ public class TbMailSender extends JavaMailSenderImpl {
setJavaMailProperties(createJavaMailProperties(jsonConfig));
}
public Boolean getOauth2Enabled() {
return oauth2Enabled;
}
public long getTokenExpires() {
return tokenExpires;
}
@Override
protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException {
updateOauth2PasswordIfExpired();
@ -98,8 +93,8 @@ public class TbMailSender extends JavaMailSenderImpl {
super.testConnection();
}
public void updateOauth2PasswordIfExpired() {
if (getOauth2Enabled() && (System.currentTimeMillis() > getTokenExpires())){
public void updateOauth2PasswordIfExpired() {
if (getOauth2Enabled() && (System.currentTimeMillis() > getTokenExpires())) {
refreshAccessToken();
setPassword(accessToken);
}
@ -168,8 +163,8 @@ public class TbMailSender extends JavaMailSenderImpl {
.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret))
.execute();
if (MailOauth2Provider.OFFICE_365.name().equals(providerId)) {
((ObjectNode)jsonValue).put("refreshToken", tokenResponse.getRefreshToken());
((ObjectNode)jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli());
((ObjectNode) jsonValue).put("refreshToken", tokenResponse.getRefreshToken());
((ObjectNode) jsonValue).put("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli());
ctx.getAdminSettingsService().saveAdminSettings(TenantId.SYS_TENANT_ID, settings);
}
accessToken = tokenResponse.getAccessToken();
@ -190,4 +185,5 @@ public class TbMailSender extends JavaMailSenderImpl {
throw new IncorrectParameterException(String.format("Invalid smtp port value: %s", strPort));
}
}
}
}

3
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java

@ -121,7 +121,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
errorPrefix = "/login?loginError=";
}
getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix +
URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8.toString()));
URLEncoder.encode(e.getMessage(), StandardCharsets.UTF_8));
}
}
@ -138,4 +138,5 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS
}
return baseUrl + "accessToken=" + tokenPair.getToken() + "&refreshToken=" + tokenPair.getRefreshToken();
}
}

2
application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java

@ -21,7 +21,7 @@ import java.util.Collections;
import java.util.Set;
public enum Resource {
ADMIN_SETTINGS(),
ADMIN_SETTINGS(EntityType.ADMIN_SETTINGS),
ALARM(EntityType.ALARM),
DEVICE(EntityType.DEVICE),
ASSET(EntityType.ASSET),

16
application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsSenderFactory.java

@ -31,16 +31,12 @@ public class DefaultSmsSenderFactory implements SmsSenderFactory {
@Override
public SmsSender createSmsSender(SmsProviderConfiguration config) {
switch (config.getType()) {
case AWS_SNS:
return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config);
case TWILIO:
return new TwilioSmsSender((TwilioSmsProviderConfiguration)config);
case SMPP:
return new SmppSmsSender((SmppSmsProviderConfiguration) config);
default:
throw new RuntimeException("Unknown SMS provider type " + config.getType());
}
return switch (config.getType()) {
case AWS_SNS -> new AwsSmsSender((AwsSnsSmsProviderConfiguration) config);
case TWILIO -> new TwilioSmsSender((TwilioSmsProviderConfiguration) config);
case SMPP -> new SmppSmsSender((SmppSmsProviderConfiguration) config);
default -> throw new RuntimeException("Unknown SMS provider type " + config.getType());
};
}
}

12
application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.sms;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NestedRuntimeException;
import org.springframework.stereotype.Service;
@ -37,8 +38,9 @@ import org.thingsboard.server.common.stats.TbApiUsageReportClient;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
@Service
@Slf4j
@Service
@RequiredArgsConstructor
public class DefaultSmsService implements SmsService {
private final SmsSenderFactory smsSenderFactory;
@ -48,13 +50,6 @@ public class DefaultSmsService implements SmsService {
private SmsSender smsSender;
public DefaultSmsService(SmsSenderFactory smsSenderFactory, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageReportClient apiUsageClient) {
this.smsSenderFactory = smsSenderFactory;
this.adminSettingsService = adminSettingsService;
this.apiUsageStateService = apiUsageStateService;
this.apiUsageClient = apiUsageClient;
}
@PostConstruct
private void init() {
updateSmsConfiguration();
@ -148,4 +143,5 @@ public class DefaultSmsService implements SmsService {
return new ThingsboardException(String.format("Unable to send SMS: %s", message),
ThingsboardErrorCode.GENERAL);
}
}

316
application/src/test/java/org/thingsboard/server/service/script/TbelInvokeDocsIoTest.java

@ -26,8 +26,10 @@ import java.util.Base64;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@ -750,6 +752,284 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected, actual);
}
// Sets
@Test
public void setsCreateNewSetFromMap_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["B", "A", "C", "A"]}
""";
decoderStr = """
var originalMap = {};
var set1 = originalMap.entrySet(); // create new Set from map, Empty
var set2 = set1.clone(); // clone new Set, Empty
var result1 = set1.addAll(msg.list); // addAll list, no sort, size = 3 ("A" - duplicate)
return {set1: set1,
set2: set2,
result1: result1
}
""";
Set expectedSet1 = new LinkedHashSet(List.of("B", "A", "C", "A"));
Set expectedSet2 = new LinkedHashSet();
Map<String, Object> expected = new LinkedHashMap<>();
expected.put("set1", expectedSet1);
expected.put("set2", expectedSet2);
expected.put("result1", true);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void setsCreateNewSetFromCreateSetTbMethod_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["B", "A", "C", "A"]}
""";
decoderStr = """
var set1 = toSet(msg.list); // create new Set from toSet() with list, no sort, size = 3 ("A" - duplicate)
var set2 = newSet(); // create new Set from newSet(), Empty
return {set1: set1,
set2: set2
}
""";
Set expectedSet1 = new LinkedHashSet(List.of("B", "A", "C", "A"));
Set expectedSet2 = new LinkedHashSet();
Map<String, Object> expected = new LinkedHashMap<>();
expected.put("set1", expectedSet1);
expected.put("set2", expectedSet2);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void setsForeachForLoop_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["A", "B", "C"]}
""";
decoderStr = """
var set2 = toSet(msg.list); // create new from list, size = 3
var set2_0 = set2.toArray()[0]; // return "A", value with index = 0 from Set
var set2Size = set2.size(); // return size = 3
var smthForeach = "";
foreach (item : set2) { // foreach for Set
smthForeach += item; // return "ABC"
}
var smthForLoop= "";
var set2Array = set2.toArray(); // for loop for Set (Set to array))
for (var i =0; i < set2.size; i++) {
smthForLoop += set2Array[i]; // return "ABC"
}
return {
set2: set2,
set2_0: set2_0,
set2Size: set2Size,
smthForeach: smthForeach,
smthForLoop: smthForLoop
}
""";
Set expectedSet2 = new LinkedHashSet(List.of("A", "B", "C"));
Map<String, Object> expected = new LinkedHashMap<>();
expected.put("set2", expectedSet2);
expected.put("set2_0", expectedSet2.toArray()[0]);
expected.put("set2Size", expectedSet2.size());
AtomicReference<String> smth = new AtomicReference<>("");
expectedSet2.forEach(s -> smth.updateAndGet(v -> v + s));
expected.put("smthForeach", smth.get());
expected.put("smthForLoop", smth.get());
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected.toString(), actual.toString());
}
/**
* add
* delete/remove
* setCreate, setCreatList
*/
@Test
public void setsAddRemove_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["B", "C", "A", "B", "C", "hello", 34567]}
""";
decoderStr = """
// add
var setAdd = toSet(["thigsboard", 4, 67]); // create new, size = 3
var setAdd1_value = setAdd.clone(); // clone setAdd, size = 3
var setAdd2_result = setAdd.add(35); // add value = 35, result = true
var setAdd2_value = setAdd.clone(); // clone setAdd (fixing the result add = 35), size = 4
var setAddList1 = toSet(msg.list); // create new from list without duplicate value ("B" and "C" - only one), size = 5
var setAdd3_result = setAdd.addAll(setAddList1); // add all without duplicate values, result = true
var setAdd3_value = setAdd.clone(); // clone setAdd (with addAll), size = 9
var setAdd4_result = setAdd.add(35); // add duplicate value = 35, result = false
var setAdd4_value = setAdd.clone(); // clone setAdd (after add duplicate value = 35), size = 9
var setAddList2 = toSet(msg.list); // create new from list without duplicate value ("B" and "C" - only one), start: size = 5, finish: size = 7
var setAdd5_result1 = setAddList2.add(72); // add is not duplicate value = 72, result = true
var setAdd5_result2 = setAddList2.add(72); // add duplicate value = 72, result = false
var setAdd5_result3 = setAddList2.add("hello25"); // add is not duplicate value = "hello25", result = true
var setAdd5_value = setAddList2.clone(); // clone setAddList2, size = 7
var setAdd6_result = setAdd.addAll(setAddList2); // add all with duplicate values, result = true
var setAdd6_value = setAdd.clone(); // clone setAdd (after addAll setAddList2), before size = 9, after size = 11, added only is not duplicate values {"hello25", 72}
// remove
var setAdd7_value = setAdd6_value.clone(); // clone setAdd6_value, before size = 11, after remove value = 4 size = 10
var setAdd7_result = setAdd7_value.remove(4); // remove value = 4, result = true
var setAdd8_value = setAdd7_value.clone(); // clone setAdd7_value, before size = 10, after clear size = 0
setAdd8_value.clear(); // setAdd8_value clear, result size = 0
return {
"setAdd1_value": setAdd1_value,
"setAdd2_result": setAdd2_result,
"setAdd2_value": setAdd2_value,
"setAddList1": setAddList1,
"setAdd3_result": setAdd3_result,
"setAdd3_value": setAdd3_value,
"setAdd4_result": setAdd4_result,
"setAdd4_value": setAdd4_value,
"setAdd5_result1": setAdd5_result1,
"setAdd5_result2": setAdd5_result2,
"setAdd5_result3": setAdd5_result3,
"setAddList2": setAddList2,
"setAdd5_value": setAdd5_value,
"setAdd6_result": setAdd6_result,
"setAdd6_value": setAdd6_value,
"setAdd7_result": setAdd7_result,
"setAdd7_value": setAdd7_value,
"setAdd8_value": setAdd8_value
};
""";
ArrayList<Object> list = new ArrayList<>(List.of("B", "C", "A", "B", "C", "hello", 34567));
ArrayList<Object> listAdd = new ArrayList<>(List.of("thigsboard", 4, 67));
Set<Object> setAdd = new LinkedHashSet<>(listAdd);
Set setAdd1_value = new LinkedHashSet<>(setAdd);
boolean setAdd2_result = setAdd.add(35);
Set<Object> setAdd2_value = new LinkedHashSet<>(setAdd);
Set<Object> setAddList1 = new LinkedHashSet<>(list);
boolean setAdd3_result = setAdd.addAll(setAddList1);
Set<Object> setAdd3_value = new LinkedHashSet<>(setAdd);
boolean setAdd4_result = setAdd.add(35);
Set<Object> setAdd4_value = new LinkedHashSet<>(setAdd);
Set<Object> setAddList2 = new LinkedHashSet<>(list);
boolean setAdd5_result1 = setAddList2.add(72);
boolean setAdd5_result2 = setAddList2.add(72);
boolean setAdd5_result3 = setAddList2.add("hello25");
Set<Object> setAdd5_value = new LinkedHashSet<>(setAddList2);
boolean setAdd6_result = setAdd.addAll(setAddList2);
Set<Object> setAdd6_value = new LinkedHashSet<>(setAdd);
// remove
Set<Object> setAdd7_value = new LinkedHashSet<>(setAdd6_value);
boolean setAdd7_result = setAdd7_value.remove(4);
Set<Object> setAdd8_value = new LinkedHashSet<>(setAdd7_value);
setAdd8_value.clear();
LinkedHashMap<String, Object> expected = new LinkedHashMap<>();
expected.put("setAdd1_value", setAdd1_value);
expected.put("setAdd2_result", setAdd2_result);
expected.put("setAdd2_value", setAdd2_value);
expected.put("setAddList1", setAddList1);
expected.put("setAdd3_result", setAdd3_result);
expected.put("setAdd3_value", setAdd3_value);
expected.put("setAdd4_result", setAdd4_result);
expected.put("setAdd4_value", setAdd4_value);
expected.put("setAdd5_result1", setAdd5_result1);
expected.put("setAdd5_result2", setAdd5_result2);
expected.put("setAdd5_result3", setAdd5_result3);
expected.put("setAddList2", setAddList2);
expected.put("setAdd5_value", setAdd5_value);
expected.put("setAdd6_result", setAdd6_result);
expected.put("setAdd6_value", setAdd6_value);
expected.put("setAdd7_result", setAdd7_result);
expected.put("setAdd7_value", setAdd7_value);
expected.put("setAdd8_value", setAdd8_value);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void setsSort_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["C", "B", "A", 34567, "B", "C", "hello", 34]}
""";
decoderStr = """
var set1 = toSet(msg.list); // create new from method toSet(List list) no sort, size = 6 ("A" and "C" is duplicated)
var set2 = toSet(msg.list); // create new from method toSet(List list) no sort, size = 6 ("A" and "C" is duplicated)
var set1_asc = set1.clone(); // clone set1, size = 6
var set1_desc = set1.clone(); // clone set1, size = 6
set1.sort(); // sort set1 -> asc
set1_asc.sort(true); // sort set1_asc -> asc
set1_desc.sort(false); // sort set1_desc -> desc
var set3 = set2.toSorted(); // toSorted set3 -> asc
var set3_asc = set2.toSorted(true); // toSorted set3 -> asc
var set3_desc = set2.toSorted(false); // toSorted set3 -> desc
return {
"set1": set1,
"set1_asc": set1_asc,
"set1_desc": set1_desc,
"set2": set2,
"set3": set3,
"set3_asc": set3_asc,
"set3_desc": set3_desc,
}
""";
ArrayList<Object> list = new ArrayList<>(List.of("C", "B", "A", 34567, "hello", 34));
Set<Object> expected = new LinkedHashSet<>(list);
ArrayList<Object> listSortAsc = new ArrayList<>(List.of(34, 34567, "A", "B", "C", "hello"));
Set<Object> expectedAsc = new LinkedHashSet<>(listSortAsc);
ArrayList<Object> listSortDesc = new ArrayList<>(List.of("hello", "C", "B", "A", 34567, 34));
Set<Object> expectedDesc = new LinkedHashSet<>(listSortDesc);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1_asc").toString());
assertEquals(expectedDesc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1_desc").toString());
assertEquals(expected.toString(), ((LinkedHashMap<?, ?>)actual).get("set2").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3_asc").toString());
assertEquals(expectedDesc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3_desc").toString());
}
@Test
public void setsContains_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["C", "B", "A", 34567, "B", "C", "hello", 34]}
""";
decoderStr = """
var set1 = toSet(msg.list); // create new from method toSet(List list) no sort, size = 6 ("A" and "C" is duplicated)
var result1 = set1.contains("A"); // return true
var result2 = set1.contains("H"); // return false
return {
"set1": set1,
"result1": result1,
"result2": result2
}
""";
List<Object> listOrigin = new ArrayList<>(List.of("C", "B", "A", 34567, "B", "C", "hello", 34));
Set<Object> expectedSet = new LinkedHashSet<>(listOrigin);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expectedSet.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(true, ((LinkedHashMap<?, ?>)actual).get("result1"));
assertEquals(false, ((LinkedHashMap<?, ?>)actual).get("result2"));
}
@Test
public void setsToList_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["C", "B", "A", 34567, "B", "C", "hello", 34]}
""";
decoderStr = """
var set1 = toSet(msg.list); // create new from method toSet(List list) no sort, size = 6 ("A" and "C" is duplicated)
var tolist = set1.toList(); // create new List from Set, size = 6
return {
"list": msg.list,
"set1": set1,
"tolist": tolist
}
""";
List<Object> listOrigin = new ArrayList<>(List.of("C", "B", "A", 34567, "B", "C", "hello", 34));
Set<Object> expectedSet = new LinkedHashSet<>(listOrigin);
List<Object> expectedToList = new ArrayList<>(expectedSet);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(listOrigin.toString(), ((LinkedHashMap<?, ?>)actual).get("list").toString());
assertEquals(expectedSet.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(expectedToList.toString(), ((LinkedHashMap<?, ?>)actual).get("tolist").toString());
}
@Test
public void arraysWillCauseArrayIndexOutOfBoundsException_Test() throws ExecutionException, InterruptedException {
msgStr = """
@ -2399,25 +2679,19 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
list.add(0x35);
return isList(list);
""");
}
@Test
public void isSet_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["C", "B", "A", 34567, "B", "C", "hello", 34]}
""";
decoderStr = """
return isSet(toSet(msg.list)); // return true
""";
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertTrue((Boolean) actual);
decoderStr = String.format("""
var list = [];
list.add(0x35);
return isMap(list);
""");
actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertFalse((Boolean) actual);
decoderStr = String.format("""
var list = [];
list.add(0x35);
return isArray(list);
""");
actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertFalse((Boolean) actual);
}
@Test
@ -2435,16 +2709,6 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertTrue((Boolean) actual);
decoderStr = """
var array = new int[3];
array[0] = 1;
array[1] = 2;
array[2] = 3;
return isList(array);
""";
actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertFalse((Boolean) actual);
}
@Test

97
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -21,8 +21,14 @@ import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.eclipse.leshan.client.LeshanClient;
import org.eclipse.leshan.client.object.Security;
import org.eclipse.leshan.client.servers.LwM2mServer;
import org.eclipse.leshan.core.ResponseCode;
import org.eclipse.leshan.core.request.ContentFormat;
import org.eclipse.leshan.core.response.ErrorCallback;
import org.eclipse.leshan.core.response.ResponseCallback;
import org.eclipse.leshan.core.response.SendResponse;
import org.eclipse.leshan.server.registration.Registration;
import org.junit.After;
import org.junit.Assert;
@ -73,6 +79,7 @@ import org.thingsboard.server.service.ws.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.transport.AbstractTransportIntegrationTest;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2mClientContext;
import org.thingsboard.server.transport.lwm2m.server.client.ResourceUpdateResult;
import org.thingsboard.server.transport.lwm2m.server.uplink.DefaultLwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
@ -82,6 +89,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@ -93,6 +101,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -351,7 +360,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
getWsClient().waitForReply();
getWsClient().registerWaitForUpdate();
this.createNewClient(security, null, false, endpoint, null, queueMode, device.getId().getId().toString());
this.createNewClient(security, null, false, endpoint, null, queueMode, device.getId().getId().toString(), null);
awaitObserveReadAll(1, lwM2MTestClient.getDeviceIdStr());
String msg = getWsClient().waitForUpdate();
@ -422,7 +431,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
getWsClient().waitForReply();
getWsClient().registerWaitForUpdate();
this.createNewClient(security, null, false, endpoint, null, true, device.getId().getId().toString());
this.createNewClient(security, null, false, endpoint, null, true, device.getId().getId().toString(), null);
awaitObserveReadAll(cntObserve, lwM2MTestClient.getDeviceIdStr());
String msg = getWsClient().waitForUpdate();
@ -543,16 +552,17 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
public void createNewClient(Security security, Security securityBs, boolean isRpc,
String endpoint, String deviceIdStr) throws Exception {
this.createNewClient(security, securityBs, isRpc, endpoint, null, false, deviceIdStr);
this.createNewClient(security, securityBs, isRpc, endpoint, null, false, deviceIdStr, null);
}
public void createNewClient(Security security, Security securityBs, boolean isRpc,
String endpoint, Integer clientDtlsCidLength, String deviceIdStr) throws Exception {
this.createNewClient(security, securityBs, isRpc, endpoint, clientDtlsCidLength, false, deviceIdStr);
this.createNewClient(security, securityBs, isRpc, endpoint, clientDtlsCidLength, false, deviceIdStr, null);
}
public void createNewClient(Security security, Security securityBs, boolean isRpc,
String endpoint, Integer clientDtlsCidLength, boolean queueMode, String deviceIdStr) throws Exception {
String endpoint, Integer clientDtlsCidLength, boolean queueMode,
String deviceIdStr, Integer value3_0_9) throws Exception {
this.clientDestroy(false);
lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint, resources);
@ -560,11 +570,86 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
int clientPort = socket.getLocalPort();
lwM2MTestClient.init(security, securityBs, clientPort, isRpc,
this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest,
clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR);
clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR, value3_0_9);
}
lwM2MTestClient.setDeviceIdStr(deviceIdStr);
}
/**
* Test: "/3/0/9" value = 44 (constant); count = 10; send from client to telemetry without observe
* @param security
* @param deviceCredentials
* @param endpoint
* @param queueMode
* @throws Exception
*/
public void testConnectionWithoutObserveWithDataReceivedSingleTelemetry(Security security,
LwM2MDeviceCredentials deviceCredentials,
String endpoint,
boolean queueMode) throws Exception {
Lwm2mDeviceProfileTransportConfiguration transportConfiguration = getTransportConfiguration(TELEMETRY_WITH_ONE_OBSERVE, getBootstrapServerCredentialsNoSec(NONE));
DeviceProfile deviceProfile = createLwm2mDeviceProfile("profileFor" + endpoint, transportConfiguration);
Device device = createLwm2mDevice(deviceCredentials, endpoint, deviceProfile.getId());
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);
getWsClient().send(cmd);
getWsClient().waitForReply();
getWsClient().registerWaitForUpdate();
this.createNewClient(security, null, false, endpoint, null, queueMode, device.getId().getId().toString(), 44);
awaitObserveReadAll(1, lwM2MTestClient.getDeviceIdStr());
LeshanClient leshanClient = lwM2MTestClient.getLeshanClient();
Map<String, LwM2mServer> registeredServers = leshanClient.getRegisteredServers();
List<String> paths = List.of("/3/0/9");
int cntUpdate = 10;
int cntLast = cntUpdate;
for (final LwM2mServer server : registeredServers.values()) {
log.info("Sending Data to {} using {}.", server, ContentFormat.SENML_CBOR);
ResponseCallback<SendResponse> responseCallback = (response) -> {
if (response.isSuccess())
log.warn("Data sent successfully to {} [{}].", server, response.getCode());
else
log.warn("Send data to {} failed [{}] : {}.", server, response.getCode(),
response.getErrorMessage() == null ? "" : response.getErrorMessage());
};
ErrorCallback errorCallback = (e) -> log.warn("Unable to send data to {}.", server, e);
while(cntLast > 0) {
leshanClient.getSendService().sendData(server, ContentFormat.SENML_CBOR, paths,
2000, responseCallback, errorCallback);
cntLast-- ;
}
}
verify(defaultUplinkMsgHandlerTest, timeout(10000).atLeast(cntUpdate))
.updateAttrTelemetry(Mockito.any(ResourceUpdateResult.class), eq(null));
String msg = getWsClient().waitForUpdate();
EntityDataUpdate update = JacksonUtil.fromString(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId());
List<EntityData> 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");
assertThat(Long.parseLong(tsValue.getValue()), instanceOf(Long.class));
int expected = 44;
assertEquals(expected, Long.parseLong(tsValue.getValue()));
}
private void clientDestroy(boolean isAfter) {
try {
if (lwM2MTestClient != null && lwM2MTestClient.getLeshanClient() != null) {

4
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/LwM2MTestClient.java

@ -144,7 +144,7 @@ public class LwM2MTestClient {
public void init(Security security, Security securityBs, int port, boolean isRpc,
LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler,
LwM2mClientContext clientContext, Integer cIdLength, boolean queueMode,
boolean supportFormatOnly_SenMLJSON_SenMLCBOR) throws InvalidDDFFileException, IOException {
boolean supportFormatOnly_SenMLJSON_SenMLCBOR, Integer value3_0_9) throws InvalidDDFFileException, IOException {
Assert.assertNull("client already initialized", leshanClient);
this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler;
this.clientContext = clientContext;
@ -197,7 +197,7 @@ public class LwM2MTestClient {
initializer.setInstancesForObject(SERVER, lwm2mServer);
}
initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor));
initializer.setInstancesForObject(DEVICE, lwM2MDevice = new SimpleLwM2MDevice(executor, value3_0_9));
initializer.setInstancesForObject(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice());
initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice());
initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class);

26
application/src/test/java/org/thingsboard/server/transport/lwm2m/client/SimpleLwM2MDevice.java

@ -85,18 +85,22 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
*/
private static Map<Integer, Long> errorCode =
Map.of(0, 0L); // 0-32
private Integer value3_0_9;
public SimpleLwM2MDevice() {
}
public SimpleLwM2MDevice(ScheduledExecutorService executorService) {
public SimpleLwM2MDevice(ScheduledExecutorService executorService, Integer value3_0_9) {
this.value3_0_9 = value3_0_9;
try {
executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(9);
fireResourceChange(20);
}
, 1, 1, TimeUnit.SECONDS); // 2 sec
if ( this.value3_0_9 == null) {
executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(9);
fireResourceChange(20);
}
, 1, 1, TimeUnit.SECONDS); // 2 sec
// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
}
} catch (Throwable e) {
log.error("[{}]Throwable", e.toString());
e.printStackTrace();
@ -211,8 +215,14 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
}
private int getBatteryLevel() {
int valBattery = randomIterator.nextInt();
log.trace("Send from client [3/0/9] val: [{}]", valBattery);
int valBattery;
if (this.value3_0_9 == null) {
valBattery = randomIterator.nextInt();
log.trace("Send from client [3/0/9] val: [{}]", valBattery);
} else {
valBattery = this.value3_0_9;
log.warn("Send from client [3/0/9] constant value: [{}]", valBattery);
}
return valBattery;
}

31
application/src/test/java/org/thingsboard/server/transport/lwm2m/rpc/sql/RpcLwm2mIntegrationDataReceivedFromClientTest.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2025 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF 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.rpc.sql;
import org.junit.Test;
import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MDeviceCredentials;
import org.thingsboard.server.transport.lwm2m.security.AbstractSecurityLwM2MIntegrationTest;
public class RpcLwm2mIntegrationDataReceivedFromClientTest extends AbstractSecurityLwM2MIntegrationTest {
@Test
public void testWithNoSecConnectLwm2mSuccessAndObserveTelemetry() throws Exception {
String clientEndpoint = CLIENT_ENDPOINT_NO_SEC;
LwM2MDeviceCredentials clientCredentials = getDeviceCredentialsNoSec(createNoSecClientCredentials(clientEndpoint));
super.testConnectionWithoutObserveWithDataReceivedSingleTelemetry(SECURITY_NO_SEC, clientCredentials, clientEndpoint, false);
}
}

5
common/dao-api/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsService.java

@ -18,8 +18,9 @@ package org.thingsboard.server.dao.settings;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.id.AdminSettingsId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.entity.EntityDaoService;
public interface AdminSettingsService {
public interface AdminSettingsService extends EntityDaoService {
AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId);
@ -31,6 +32,4 @@ public interface AdminSettingsService {
boolean deleteAdminSettingsByTenantIdAndKey(TenantId tenantId, String key);
void deleteAdminSettingsByTenantId(TenantId tenantId);
}

3
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -15,9 +15,6 @@
*/
package org.thingsboard.server.common.data;
/**
* @author Andrew Shvayka
*/
public class DataConstants {
public static final String TENANT = "TENANT";

6
common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java

@ -22,9 +22,6 @@ import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
/**
* @author Andrew Shvayka
*/
public enum EntityType {
TENANT(1),
CUSTOMER(2),
@ -65,7 +62,8 @@ public enum EntityType {
MOBILE_APP_BUNDLE(38),
CALCULATED_FIELD(39),
CALCULATED_FIELD_LINK(40),
JOB(41);
JOB(41),
ADMIN_SETTINGS(42);
@Getter
private final int protoNumber; // Corresponds to EntityTypeProto

18
common/data/src/main/java/org/thingsboard/server/common/data/id/AdminSettingsId.java

@ -17,14 +17,26 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.EntityType;
import java.io.Serial;
import java.util.UUID;
public class AdminSettingsId extends UUIDBased {
public class AdminSettingsId extends UUIDBased implements EntityId {
@Serial
private static final long serialVersionUID = -4208011957475806567L;
@JsonCreator
public AdminSettingsId(@JsonProperty("id") UUID id){
public AdminSettingsId(@JsonProperty("id") UUID id) {
super(id);
}
@Schema(requiredMode = Schema.RequiredMode.REQUIRED, description = "string", example = "ADMIN_SETTINGS", allowableValues = "ADMIN_SETTINGS")
@Override
public EntityType getEntityType() {
return EntityType.ADMIN_SETTINGS;
}
}

3
common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java

@ -23,10 +23,12 @@ import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.thingsboard.server.common.data.EntityType;
import java.io.Serial;
import java.util.UUID;
public class EdgeId extends UUIDBased implements EntityId {
@Serial
private static final long serialVersionUID = 1L;
@JsonIgnore
@ -51,4 +53,5 @@ public class EdgeId extends UUIDBased implements EntityId {
public static EdgeId fromUUID(@JsonProperty("id") UUID id) {
return edges.computeIfAbsent(id, EdgeId::new);
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java

@ -24,10 +24,6 @@ import org.thingsboard.server.common.data.EntityType;
import java.io.Serializable;
import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@JsonDeserialize(using = EntityIdDeserializer.class)
@JsonSerialize(using = EntityIdSerializer.class)
@Schema

190
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -20,9 +20,6 @@ import org.thingsboard.server.common.data.edge.EdgeEventType;
import java.util.UUID;
/**
* Created by ashvayka on 25.04.17.
*/
public class EntityIdFactory {
public static EntityId getByTypeAndUuid(int type, String uuid) {
@ -50,131 +47,74 @@ public class EntityIdFactory {
}
public static EntityId getByTypeAndUuid(EntityType type, UUID uuid) {
switch (type) {
case TENANT:
return TenantId.fromUUID(uuid);
case CUSTOMER:
return new CustomerId(uuid);
case USER:
return new UserId(uuid);
case DASHBOARD:
return new DashboardId(uuid);
case DEVICE:
return new DeviceId(uuid);
case ASSET:
return new AssetId(uuid);
case ALARM:
return new AlarmId(uuid);
case RULE_CHAIN:
return new RuleChainId(uuid);
case RULE_NODE:
return new RuleNodeId(uuid);
case ENTITY_VIEW:
return new EntityViewId(uuid);
case WIDGETS_BUNDLE:
return new WidgetsBundleId(uuid);
case WIDGET_TYPE:
return new WidgetTypeId(uuid);
case DEVICE_PROFILE:
return new DeviceProfileId(uuid);
case ASSET_PROFILE:
return new AssetProfileId(uuid);
case TENANT_PROFILE:
return new TenantProfileId(uuid);
case API_USAGE_STATE:
return new ApiUsageStateId(uuid);
case TB_RESOURCE:
return new TbResourceId(uuid);
case OTA_PACKAGE:
return new OtaPackageId(uuid);
case EDGE:
return new EdgeId(uuid);
case RPC:
return new RpcId(uuid);
case QUEUE:
return new QueueId(uuid);
case NOTIFICATION_TARGET:
return new NotificationTargetId(uuid);
case NOTIFICATION_REQUEST:
return new NotificationRequestId(uuid);
case NOTIFICATION_RULE:
return new NotificationRuleId(uuid);
case NOTIFICATION_TEMPLATE:
return new NotificationTemplateId(uuid);
case NOTIFICATION:
return new NotificationId(uuid);
case QUEUE_STATS:
return new QueueStatsId(uuid);
case OAUTH2_CLIENT:
return new OAuth2ClientId(uuid);
case MOBILE_APP:
return new MobileAppId(uuid);
case DOMAIN:
return new DomainId(uuid);
case MOBILE_APP_BUNDLE:
return new MobileAppBundleId(uuid);
case CALCULATED_FIELD:
return new CalculatedFieldId(uuid);
case CALCULATED_FIELD_LINK:
return new CalculatedFieldLinkId(uuid);
case JOB:
return new JobId(uuid);
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
return switch (type) {
case TENANT -> TenantId.fromUUID(uuid);
case CUSTOMER -> new CustomerId(uuid);
case USER -> new UserId(uuid);
case DASHBOARD -> new DashboardId(uuid);
case DEVICE -> new DeviceId(uuid);
case ASSET -> new AssetId(uuid);
case ALARM -> new AlarmId(uuid);
case RULE_CHAIN -> new RuleChainId(uuid);
case RULE_NODE -> new RuleNodeId(uuid);
case ENTITY_VIEW -> new EntityViewId(uuid);
case WIDGETS_BUNDLE -> new WidgetsBundleId(uuid);
case WIDGET_TYPE -> new WidgetTypeId(uuid);
case DEVICE_PROFILE -> new DeviceProfileId(uuid);
case ASSET_PROFILE -> new AssetProfileId(uuid);
case TENANT_PROFILE -> new TenantProfileId(uuid);
case API_USAGE_STATE -> new ApiUsageStateId(uuid);
case TB_RESOURCE -> new TbResourceId(uuid);
case OTA_PACKAGE -> new OtaPackageId(uuid);
case EDGE -> EdgeId.fromUUID(uuid);
case RPC -> new RpcId(uuid);
case QUEUE -> new QueueId(uuid);
case NOTIFICATION_TARGET -> new NotificationTargetId(uuid);
case NOTIFICATION_REQUEST -> new NotificationRequestId(uuid);
case NOTIFICATION_RULE -> new NotificationRuleId(uuid);
case NOTIFICATION_TEMPLATE -> new NotificationTemplateId(uuid);
case NOTIFICATION -> new NotificationId(uuid);
case QUEUE_STATS -> new QueueStatsId(uuid);
case OAUTH2_CLIENT -> new OAuth2ClientId(uuid);
case MOBILE_APP -> new MobileAppId(uuid);
case DOMAIN -> new DomainId(uuid);
case MOBILE_APP_BUNDLE -> new MobileAppBundleId(uuid);
case CALCULATED_FIELD -> new CalculatedFieldId(uuid);
case CALCULATED_FIELD_LINK -> new CalculatedFieldLinkId(uuid);
case JOB -> new JobId(uuid);
case ADMIN_SETTINGS -> new AdminSettingsId(uuid);
default -> throw new IllegalArgumentException("EntityType " + type + " is not supported!");
};
}
public static EntityId getByEdgeEventTypeAndUuid(EdgeEventType edgeEventType, UUID uuid) {
switch (edgeEventType) {
case TENANT:
return TenantId.fromUUID(uuid);
case CUSTOMER:
return new CustomerId(uuid);
case USER:
return new UserId(uuid);
case DASHBOARD:
return new DashboardId(uuid);
case DEVICE:
return new DeviceId(uuid);
case ASSET:
return new AssetId(uuid);
case ALARM:
return new AlarmId(uuid);
case RULE_CHAIN:
return new RuleChainId(uuid);
case ENTITY_VIEW:
return new EntityViewId(uuid);
case WIDGETS_BUNDLE:
return new WidgetsBundleId(uuid);
case WIDGET_TYPE:
return new WidgetTypeId(uuid);
case DEVICE_PROFILE:
return new DeviceProfileId(uuid);
case ASSET_PROFILE:
return new AssetProfileId(uuid);
case TENANT_PROFILE:
return new TenantProfileId(uuid);
case OTA_PACKAGE:
return new OtaPackageId(uuid);
case EDGE:
return new EdgeId(uuid);
case QUEUE:
return new QueueId(uuid);
case TB_RESOURCE:
return new TbResourceId(uuid);
case NOTIFICATION_RULE:
return new NotificationRuleId(uuid);
case NOTIFICATION_TARGET:
return new NotificationTargetId(uuid);
case NOTIFICATION_TEMPLATE:
return new NotificationTemplateId(uuid);
case OAUTH2_CLIENT:
return new OAuth2ClientId(uuid);
case DOMAIN:
return new DomainId(uuid);
case CALCULATED_FIELD:
return new CalculatedFieldId(uuid);
}
throw new IllegalArgumentException("EdgeEventType " + edgeEventType + " is not supported!");
return switch (edgeEventType) {
case TENANT -> TenantId.fromUUID(uuid);
case CUSTOMER -> new CustomerId(uuid);
case USER -> new UserId(uuid);
case DASHBOARD -> new DashboardId(uuid);
case DEVICE -> new DeviceId(uuid);
case ASSET -> new AssetId(uuid);
case ALARM -> new AlarmId(uuid);
case RULE_CHAIN -> new RuleChainId(uuid);
case ENTITY_VIEW -> new EntityViewId(uuid);
case WIDGETS_BUNDLE -> new WidgetsBundleId(uuid);
case WIDGET_TYPE -> new WidgetTypeId(uuid);
case DEVICE_PROFILE -> new DeviceProfileId(uuid);
case ASSET_PROFILE -> new AssetProfileId(uuid);
case TENANT_PROFILE -> new TenantProfileId(uuid);
case OTA_PACKAGE -> new OtaPackageId(uuid);
case EDGE -> EdgeId.fromUUID(uuid);
case QUEUE -> new QueueId(uuid);
case TB_RESOURCE -> new TbResourceId(uuid);
case NOTIFICATION_RULE -> new NotificationRuleId(uuid);
case NOTIFICATION_TARGET -> new NotificationTargetId(uuid);
case NOTIFICATION_TEMPLATE -> new NotificationTemplateId(uuid);
case OAUTH2_CLIENT -> new OAuth2ClientId(uuid);
case DOMAIN -> new DomainId(uuid);
case CALCULATED_FIELD -> new CalculatedFieldId(uuid);
default -> throw new IllegalArgumentException("EdgeEventType " + edgeEventType + " is not supported!");
};
}
}

4
common/data/src/main/java/org/thingsboard/server/common/data/id/JobId.java

@ -20,10 +20,14 @@ import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.EntityType;
import java.io.Serial;
import java.util.UUID;
public class JobId extends UUIDBased implements EntityId {
@Serial
private static final long serialVersionUID = -2225072123132918395L;
@JsonCreator
public JobId(@JsonProperty("id") UUID id) {
super(id);

2
common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java

@ -23,6 +23,7 @@ import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.thingsboard.server.common.data.EntityType;
import java.io.Serial;
import java.util.UUID;
public final class TenantId extends UUIDBased implements EntityId {
@ -33,6 +34,7 @@ public final class TenantId extends UUIDBased implements EntityId {
@JsonIgnore
public static final TenantId SYS_TENANT_ID = TenantId.fromUUID(EntityId.NULL_UUID);
@Serial
private static final long serialVersionUID = 1L;
@JsonCreator

12
common/proto/src/main/java/org/thingsboard/server/common/util/ProtoUtils.java

@ -252,7 +252,7 @@ public class ProtoUtils {
public static EdgeEvent fromProto(TransportProtos.EdgeEventMsgProto proto) {
EdgeEvent edgeEvent = new EdgeEvent();
TenantId tenantId = new TenantId(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB()));
TenantId tenantId = TenantId.fromUUID(new UUID(proto.getTenantIdMSB(), proto.getTenantIdLSB()));
edgeEvent.setTenantId(tenantId);
edgeEvent.setType(EdgeEventType.valueOf(proto.getEntityType()));
edgeEvent.setAction(EdgeEventActionType.valueOf(proto.getAction()));
@ -845,7 +845,7 @@ public class ProtoUtils {
public static Device fromProto(TransportProtos.DeviceProto proto) {
Device device = new Device(getEntityId(proto.getDeviceIdMSB(), proto.getDeviceIdLSB(), DeviceId::new));
device.setCreatedTime(proto.getCreatedTime());
device.setTenantId(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::new));
device.setTenantId(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::fromUUID));
device.setName(proto.getDeviceName());
device.setType(proto.getDeviceType());
device.setDeviceProfileId(getEntityId(proto.getDeviceProfileIdMSB(), proto.getDeviceProfileIdLSB(), DeviceProfileId::new));
@ -937,7 +937,7 @@ public class ProtoUtils {
public static DeviceProfile fromProto(TransportProtos.DeviceProfileProto proto) {
DeviceProfile deviceProfile = new DeviceProfile(getEntityId(proto.getDeviceProfileIdMSB(), proto.getDeviceProfileIdLSB(), DeviceProfileId::new));
deviceProfile.setCreatedTime(proto.getCreatedTime());
deviceProfile.setTenantId(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::new));
deviceProfile.setTenantId(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::fromUUID));
deviceProfile.setName(proto.getName());
deviceProfile.setDefault(proto.getIsDefault());
deviceProfile.setType(DeviceProfileType.valueOf(proto.getType()));
@ -1028,7 +1028,7 @@ public class ProtoUtils {
}
public static Tenant fromProto(TransportProtos.TenantProto proto) {
Tenant tenant = new Tenant(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::new));
Tenant tenant = new Tenant(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::fromUUID));
tenant.setCreatedTime(proto.getCreatedTime());
tenant.setTenantProfileId(getEntityId(proto.getTenantProfileIdMSB(), proto.getTenantProfileIdLSB(), TenantProfileId::new));
tenant.setTitle(proto.getTitle());
@ -1142,7 +1142,7 @@ public class ProtoUtils {
public static TbResource fromProto(TransportProtos.TbResourceProto proto) {
TbResource resource = new TbResource(getEntityId(proto.getResourceIdMSB(), proto.getResourceIdLSB(), TbResourceId::new));
resource.setTenantId(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::new));
resource.setTenantId(getEntityId(proto.getTenantIdMSB(), proto.getTenantIdLSB(), TenantId::fromUUID));
resource.setCreatedTime(proto.getCreatedTime());
resource.setTitle(proto.getTitle());
resource.setResourceType(ResourceType.valueOf(proto.getResourceType()));
@ -1198,7 +1198,7 @@ public class ProtoUtils {
public static ApiUsageState fromProto(TransportProtos.ApiUsageStateProto proto) {
ApiUsageState apiUsageState = new ApiUsageState(getEntityId(proto.getApiUsageStateIdMSB(), proto.getApiUsageStateIdLSB(), ApiUsageStateId::new));
apiUsageState.setTenantId(getEntityId(proto.getTenantProfileIdMSB(), proto.getTenantProfileIdLSB(), TenantId::new));
apiUsageState.setTenantId(getEntityId(proto.getTenantProfileIdMSB(), proto.getTenantProfileIdLSB(), TenantId::fromUUID));
apiUsageState.setCreatedTime(proto.getCreatedTime());
apiUsageState.setEntityId(EntityIdFactory.getByTypeAndUuid(fromProto(proto.getEntityType()), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB())));
apiUsageState.setTransportState(ApiUsageStateValue.valueOf(proto.getTransportState()));

1
common/proto/src/main/proto/queue.proto

@ -64,6 +64,7 @@ enum EntityTypeProto {
CALCULATED_FIELD = 39;
CALCULATED_FIELD_LINK = 40;
JOB = 41;
ADMIN_SETTINGS = 42;
}
enum ApiUsageRecordKeyProto {

21
common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java

@ -23,6 +23,7 @@ import org.mvel2.ExecutionContext;
import org.mvel2.ParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap;
import org.mvel2.execution.ExecutionLinkedHashSet;
import org.mvel2.util.MethodStub;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.geo.Coordinates;
@ -46,6 +47,7 @@ import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -386,6 +388,12 @@ public class TbUtils {
Object.class)));
parserConfig.addImport("isArray", new MethodStub(TbUtils.class.getMethod("isArray",
Object.class)));
parserConfig.addImport("newSet", new MethodStub(TbUtils.class.getMethod("newSet",
ExecutionContext.class)));
parserConfig.addImport("toSet", new MethodStub(TbUtils.class.getMethod("toSet",
ExecutionContext.class, List.class)));
parserConfig.addImport("isSet", new MethodStub(TbUtils.class.getMethod("isSet",
Object.class)));
}
public static String btoa(String input) {
@ -1481,6 +1489,19 @@ public class TbUtils {
return obj != null && obj.getClass().isArray();
}
public static <E> Set<E> newSet(ExecutionContext ctx) {
return new ExecutionLinkedHashSet<>(ctx);
}
public static <E> Set<E> toSet(ExecutionContext ctx, List<E> list) {
Set<E> newSet = new LinkedHashSet<>(list);
return new ExecutionLinkedHashSet<>(newSet, ctx);
}
public static boolean isSet(Object obj) {
return obj instanceof Set;
}
private static byte isValidIntegerToByte(Integer val) {
if (val > 255 || val < -128) {
throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " +

68
common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java

@ -28,6 +28,7 @@ import org.mvel2.ParserContext;
import org.mvel2.SandboxedParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap;
import org.mvel2.execution.ExecutionLinkedHashSet;
import java.io.IOException;
import java.math.BigDecimal;
@ -39,14 +40,18 @@ import java.util.Base64;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import static java.lang.Character.MAX_RADIX;
import static java.lang.Character.MIN_RADIX;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Slf4j
@ -1184,10 +1189,11 @@ public class TbUtilsTest {
@Test
public void isList() throws ExecutionException, InterruptedException {
List<Integer> liat = List.of(0x35);
assertTrue(TbUtils.isList(liat));
assertFalse(TbUtils.isMap(liat));
assertFalse(TbUtils.isArray(liat));
List<Integer> list = List.of(0x35);
assertTrue(TbUtils.isList(list));
assertFalse(TbUtils.isMap(list));
assertFalse(TbUtils.isArray(list));
assertFalse(TbUtils.isSet(list));
}
@Test
@ -1195,6 +1201,52 @@ public class TbUtilsTest {
byte [] array = new byte[]{1, 2, 3};
assertTrue(TbUtils.isArray(array));
assertFalse(TbUtils.isList(array));
assertFalse(TbUtils.isSet(array));
}
@Test
public void isSet() throws ExecutionException, InterruptedException {
Set<Byte> set = toSet(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xBB, (byte) 0xAA});
assertTrue(TbUtils.isSet(set));
assertFalse(TbUtils.isList(set));
assertFalse(TbUtils.isArray(set));
}
@Test
public void setTest() throws ExecutionException, InterruptedException {
Set actual = TbUtils.newSet(ctx);
Set expected = toSet(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xCC});
actual.add((byte) 0xDD);
actual.add((byte) 0xCC);
actual.add((byte) 0xCC);
assertTrue(expected.containsAll(actual));
List list = toList(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xBB, (byte) 0xAA});
actual.addAll(list);
assertEquals(4, actual.size());
assertTrue(actual.containsAll(expected));
actual = TbUtils.toSet(ctx, list);
expected = toSet(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xDA});
actual.add((byte) 0xDA);
actual.remove((byte) 0xBB);
actual.remove((byte) 0xAA);
assertTrue(expected.containsAll(actual));
assertEquals(actual.size(), 3);
actual.clear();
assertTrue(actual.isEmpty());
actual = TbUtils.toSet(ctx, list);
Set actualClone = TbUtils.toSet(ctx, list);
Set actualClone_asc = TbUtils.toSet(ctx, list);
Set actualClone_desc = TbUtils.toSet(ctx, list);
((ExecutionLinkedHashSet<?>)actualClone).sort();
((ExecutionLinkedHashSet<?>)actualClone_asc).sort(true);
((ExecutionLinkedHashSet<?>)actualClone_desc).sort(false);
assertEquals(list.toString(), actual.toString());
assertNotEquals(list.toString(), actualClone.toString());
Collections.sort(list);
assertEquals(list.toString(), actualClone.toString());
assertEquals(list.toString(), actualClone_asc.toString());
Collections.sort(list, Collections.reverseOrder());
assertNotEquals(list.toString(), actualClone_asc.toString());
assertEquals(list.toString(), actualClone_desc.toString());
}
private static List<Byte> toList(byte[] data) {
@ -1204,5 +1256,13 @@ public class TbUtilsTest {
}
return result;
}
private static Set<Byte> toSet(byte[] data) {
Set<Byte> result = new LinkedHashSet<>();
for (Byte b : data) {
result.add(b);
}
return result;
}
}

2
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/uplink/DefaultLwM2mUplinkMsgHandler.java

@ -383,7 +383,7 @@ public class DefaultLwM2mUplinkMsgHandler extends LwM2MExecutorAwareService impl
LwM2mPath path = instant.getKey();
LwM2mNode node = instant.getValue();
LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint());
ObjectModel objectModelVersion = lwM2MClient.getObjectModel(path.toString(), modelProvider);
ObjectModel objectModelVersion = lwM2MClient.getObjectModel(convertObjectIdToVersionedId(path.toString(), lwM2MClient), modelProvider);
if (objectModelVersion != null) {
ResourceUpdateResult updateResource = new ResourceUpdateResult(lwM2MClient);
if (node instanceof LwM2mObject) {

10
dao/src/main/java/org/thingsboard/server/dao/oauth2/HybridClientRegistrationRepository.java

@ -29,6 +29,7 @@ import java.util.UUID;
@Component
public class HybridClientRegistrationRepository implements ClientRegistrationRepository {
private static final String defaultRedirectUriTemplate = "{baseUrl}/login/oauth2/code/{registrationId}";
@Autowired
@ -37,11 +38,13 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep
@Override
public ClientRegistration findByRegistrationId(String registrationId) {
OAuth2Client oAuth2Client = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2ClientId(UUID.fromString(registrationId)));
return oAuth2Client == null ?
null : toSpringClientRegistration(oAuth2Client);
if (oAuth2Client == null) {
return null;
}
return toSpringClientRegistration(oAuth2Client);
}
private ClientRegistration toSpringClientRegistration(OAuth2Client oAuth2Client){
private ClientRegistration toSpringClientRegistration(OAuth2Client oAuth2Client) {
String registrationId = oAuth2Client.getUuidId().toString();
// NONE is used if we need pkce-based code challenge
@ -67,4 +70,5 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep
.redirectUri(defaultRedirectUriTemplate)
.build();
}
}

14
dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsDao.java

@ -23,20 +23,8 @@ import java.util.UUID;
public interface AdminSettingsDao extends Dao<AdminSettings> {
/**
* Save or update admin settings object
*
* @param adminSettings the admin settings object
* @return saved admin settings object
*/
AdminSettings save(TenantId tenantId, AdminSettings adminSettings);
/**
* Find admin settings by key.
*
* @param key the key
* @return the admin settings object
*/
AdminSettings findByTenantIdAndKey(UUID tenantId, String key);
boolean removeByTenantIdAndKey(UUID tenantId, String key);

22
dao/src/main/java/org/thingsboard/server/dao/settings/AdminSettingsServiceImpl.java

@ -21,11 +21,16 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.AdminSettingsId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.Validator;
import java.util.Optional;
@Service
@Slf4j
public class AdminSettingsServiceImpl implements AdminSettingsService {
@ -87,10 +92,25 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
}
@Override
public void deleteAdminSettingsByTenantId(TenantId tenantId) {
public void deleteByTenantId(TenantId tenantId) {
adminSettingsDao.removeByTenantId(tenantId.getId());
}
@Override
public void deleteEntity(TenantId tenantId, EntityId id, boolean force) {
adminSettingsDao.removeById(tenantId, id.getId());
}
@Override
public Optional<HasId<?>> findEntity(TenantId tenantId, EntityId entityId) {
return Optional.ofNullable(adminSettingsDao.findById(tenantId, entityId.getId()));
}
@Override
public EntityType getEntityType() {
return EntityType.ADMIN_SETTINGS;
}
private void dropTokenIfProviderInfoChanged(JsonNode newJsonValue, JsonNode oldJsonValue) {
if (newJsonValue.has("enableOauth2") && newJsonValue.get("enableOauth2").asBoolean()) {
if (!newJsonValue.get("providerId").equals(oldJsonValue.get("providerId")) ||

3
dao/src/main/java/org/thingsboard/server/dao/sql/settings/AdminSettingsRepository.java

@ -22,9 +22,6 @@ import org.thingsboard.server.dao.model.sql.AdminSettingsEntity;
import java.util.UUID;
/**
* Created by Valerii Sosliuk on 5/6/2017.
*/
public interface AdminSettingsRepository extends JpaRepository<AdminSettingsEntity, UUID> {
AdminSettingsEntity findByTenantIdAndKey(UUID tenantId, String key);

34
dao/src/main/java/org/thingsboard/server/dao/sql/settings/JpaAdminSettingsDao.java

@ -15,12 +15,12 @@
*/
package org.thingsboard.server.dao.sql.settings;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
@ -35,21 +35,10 @@ import java.util.UUID;
@Component
@SqlDao
@Slf4j
@RequiredArgsConstructor
public class JpaAdminSettingsDao extends JpaAbstractDao<AdminSettingsEntity, AdminSettings> implements AdminSettingsDao, TenantEntityDao<AdminSettings> {
@Autowired
private AdminSettingsRepository adminSettingsRepository;
@Override
protected Class<AdminSettingsEntity> getEntityClass() {
return AdminSettingsEntity.class;
}
@Override
protected JpaRepository<AdminSettingsEntity, UUID> getRepository() {
return adminSettingsRepository;
}
private final AdminSettingsRepository adminSettingsRepository;
@Override
public AdminSettings findByTenantIdAndKey(UUID tenantId, String key) {
@ -77,4 +66,19 @@ public class JpaAdminSettingsDao extends JpaAbstractDao<AdminSettingsEntity, Adm
return DaoUtil.toPageData(adminSettingsRepository.findByTenantId(tenantId.getId(), DaoUtil.toPageable(pageLink)));
}
@Override
protected Class<AdminSettingsEntity> getEntityClass() {
return AdminSettingsEntity.class;
}
@Override
protected JpaRepository<AdminSettingsEntity, UUID> getRepository() {
return adminSettingsRepository;
}
@Override
public EntityType getEntityType() {
return EntityType.ADMIN_SETTINGS;
}
}

8
dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java

@ -43,7 +43,6 @@ import org.thingsboard.server.dao.notification.NotificationSettingsService;
import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.TenantDataValidator;
import org.thingsboard.server.dao.settings.AdminSettingsService;
import org.thingsboard.server.dao.trendz.TrendzSettingsService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.dao.user.UserService;
@ -76,8 +75,6 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
@Autowired
private ApiUsageStateService apiUsageStateService;
@Autowired
private AdminSettingsService adminSettingsService;
@Autowired
private NotificationSettingsService notificationSettingsService;
@Autowired
private QrCodeSettingService qrCodeSettingService;
@ -168,7 +165,6 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
userService.deleteAllByTenantId(tenantId);
notificationSettingsService.deleteNotificationSettings(tenantId);
trendzSettingsService.deleteTrendzSettings(tenantId);
adminSettingsService.deleteAdminSettingsByTenantId(tenantId);
qrCodeSettingService.deleteByTenantId(tenantId);
tenantDao.removeById(tenantId, tenantId.getId());
@ -176,7 +172,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(tenantId).entity(tenant).build());
cleanUpService.removeTenantEntities(tenantId, // don't forget to implement deleteEntity from EntityDaoService when adding entity type to this list
EntityType.JOB, EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE, EntityType.WIDGET_TYPE,
EntityType.ADMIN_SETTINGS, EntityType.JOB, EntityType.ENTITY_VIEW, EntityType.WIDGETS_BUNDLE, EntityType.WIDGET_TYPE,
EntityType.ASSET, EntityType.ASSET_PROFILE, EntityType.DEVICE, EntityType.DEVICE_PROFILE,
EntityType.DASHBOARD, EntityType.EDGE, EntityType.RULE_CHAIN, EntityType.API_USAGE_STATE,
EntityType.TB_RESOURCE, EntityType.OTA_PACKAGE, EntityType.RPC, EntityType.QUEUE,
@ -230,7 +226,7 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
return existsTenantCache.getAndPutInTransaction(tenantId, () -> tenantDao.existsById(tenantId, tenantId.getId()), false);
}
private PaginatedRemover<TenantId, Tenant> tenantsRemover = new PaginatedRemover<>() {
private final PaginatedRemover<TenantId, Tenant> tenantsRemover = new PaginatedRemover<>() {
@Override
protected PageData<Tenant> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) {

7
netty-mqtt/src/main/java/org/thingsboard/mqtt/ChannelClosedException.java

@ -15,11 +15,11 @@
*/
package org.thingsboard.mqtt;
/**
* Created by Valerii Sosliuk on 12/26/2017.
*/
import java.io.Serial;
public class ChannelClosedException extends RuntimeException {
@Serial
private static final long serialVersionUID = 6266638352424706909L;
public ChannelClosedException() {
@ -40,4 +40,5 @@ public class ChannelClosedException extends RuntimeException {
public ChannelClosedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}

4
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientCallback.java

@ -21,9 +21,6 @@ import io.netty.handler.codec.mqtt.MqttPubAckMessage;
import io.netty.handler.codec.mqtt.MqttSubAckMessage;
import io.netty.handler.codec.mqtt.MqttUnsubAckMessage;
/**
* Created by Valerii Sosliuk on 12/30/2017.
*/
public interface MqttClientCallback {
/**
@ -53,4 +50,5 @@ public interface MqttClientCallback {
default void onDisconnect(MqttMessage mqttDisconnectMessage) {
}
}

124
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java

@ -28,23 +28,46 @@ import java.util.Random;
@SuppressWarnings({"WeakerAccess", "unused"})
public final class MqttClientConfig {
@Getter
private final SslContext sslContext;
private final String randomClientId;
@Getter
@Setter
private String ownerId; // [TenantId][IntegrationId] or [TenantId][RuleNodeId] for exceptions logging purposes
@Nonnull
@Getter
private String clientId;
@Getter
private int timeoutSeconds = 60;
@Getter
private MqttVersion protocolVersion = MqttVersion.MQTT_3_1;
@Nullable private String username = null;
@Nullable private String password = null;
@Nullable
@Getter
@Setter
private String username = null;
@Nullable
@Getter
@Setter
private String password = null;
@Getter
@Setter
private boolean cleanSession = true;
@Nullable private MqttLastWill lastWill;
@Nullable
@Getter
@Setter
private MqttLastWill lastWill;
@Setter
@Getter
private Class<? extends Channel> channelClass = NioSocketChannel.class;
@Getter
@Setter
private boolean reconnect = true;
@Getter
private long reconnectDelay = 1L;
@Getter
private int maxBytesInMessage = 8092;
@Getter
@ -74,109 +97,37 @@ public final class MqttClientConfig {
public MqttClientConfig(SslContext sslContext) {
this.sslContext = sslContext;
Random random = new Random();
String id = "netty-mqtt/";
StringBuilder id = new StringBuilder("netty-mqtt/");
String[] options = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
for(int i = 0; i < 8; i++){
id += options[random.nextInt(options.length)];
for (int i = 0; i < 8; i++) {
id.append(options[random.nextInt(options.length)]);
}
this.clientId = id;
this.randomClientId = id;
}
@Nonnull
public String getClientId() {
return clientId;
this.clientId = id.toString();
this.randomClientId = id.toString();
}
public void setClientId(@Nullable String clientId) {
if(clientId == null){
if (clientId == null) {
this.clientId = randomClientId;
}else{
} else {
this.clientId = clientId;
}
}
public int getTimeoutSeconds() {
return timeoutSeconds;
}
public void setTimeoutSeconds(int timeoutSeconds) {
if(timeoutSeconds != -1 && timeoutSeconds <= 0){
if (timeoutSeconds != -1 && timeoutSeconds <= 0) {
throw new IllegalArgumentException("timeoutSeconds must be > 0 or -1");
}
this.timeoutSeconds = timeoutSeconds;
}
public MqttVersion getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(MqttVersion protocolVersion) {
if(protocolVersion == null){
if (protocolVersion == null) {
throw new NullPointerException("protocolVersion");
}
this.protocolVersion = protocolVersion;
}
@Nullable
public String getUsername() {
return username;
}
public void setUsername(@Nullable String username) {
this.username = username;
}
@Nullable
public String getPassword() {
return password;
}
public void setPassword(@Nullable String password) {
this.password = password;
}
public boolean isCleanSession() {
return cleanSession;
}
public void setCleanSession(boolean cleanSession) {
this.cleanSession = cleanSession;
}
@Nullable
public MqttLastWill getLastWill() {
return lastWill;
}
public void setLastWill(@Nullable MqttLastWill lastWill) {
this.lastWill = lastWill;
}
public Class<? extends Channel> getChannelClass() {
return channelClass;
}
public void setChannelClass(Class<? extends Channel> channelClass) {
this.channelClass = channelClass;
}
public SslContext getSslContext() {
return sslContext;
}
public boolean isReconnect() {
return reconnect;
}
public void setReconnect(boolean reconnect) {
this.reconnect = reconnect;
}
public long getReconnectDelay() {
return reconnectDelay;
}
/**
* Sets the reconnect delay in seconds. Defaults to 1 second.
* @param reconnectDelay
@ -189,10 +140,6 @@ public final class MqttClientConfig {
this.reconnectDelay = reconnectDelay;
}
public int getMaxBytesInMessage() {
return maxBytesInMessage;
}
/**
* Sets the maximum number of bytes in the message for the {@link io.netty.handler.codec.mqtt.MqttDecoder}.
* Default value is 8092 as specified by Netty. The absolute maximum size is 256MB as set by the MQTT spec.
@ -206,4 +153,5 @@ public final class MqttClientConfig {
}
this.maxBytesInMessage = maxBytesInMessage;
}
}

77
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java

@ -46,7 +46,9 @@ import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultPromise;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ListeningExecutor;
@ -67,18 +69,28 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
final class MqttClientImpl implements MqttClient {
@Getter(AccessLevel.PACKAGE)
private final Set<String> serverSubscriptions = new HashSet<>();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<Integer, MqttPendingUnsubscription> pendingServerUnsubscribes = new ConcurrentHashMap<>();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<Integer, MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new ConcurrentHashMap<>();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<Integer, MqttPendingPublish> pendingPublishes = new ConcurrentHashMap<>();
@Getter(AccessLevel.PACKAGE)
private final HashMultimap<String, MqttSubscription> subscriptions = HashMultimap.create();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<Integer, MqttPendingSubscription> pendingSubscriptions = new ConcurrentHashMap<>();
@Getter(AccessLevel.PACKAGE)
private final Set<String> pendingSubscribeTopics = new HashSet<>();
@Getter(AccessLevel.PACKAGE)
private final HashMultimap<MqttHandler, MqttSubscription> handlerToSubscription = HashMultimap.create();
private final AtomicInteger nextMessageId = new AtomicInteger(1);
@Getter
private final MqttClientConfig clientConfig;
@Getter(AccessLevel.PACKAGE)
private final MqttHandler defaultHandler;
private final ReconnectStrategy reconnectStrategy;
@ -88,12 +100,15 @@ final class MqttClientImpl implements MqttClient {
private volatile Channel channel;
private volatile boolean disconnected = false;
@Getter
private volatile boolean reconnect = false;
private String host;
private int port;
@Getter
@Setter
private MqttClientCallback callback;
@Getter
private final ListeningExecutor handlerExecutor;
private final static int DISCONNECT_FALLBACK_DELAY_SECS = 1;
@ -240,11 +255,6 @@ final class MqttClientImpl implements MqttClient {
this.eventLoop = eventLoop;
}
@Override
public ListeningExecutor getHandlerExecutor() {
return this.handlerExecutor;
}
/**
* Subscribe on the given topic. When a message is received, MqttClient will invoke the {@link MqttHandler#onMessage(String, ByteBuf)} function of the given handler
*
@ -446,16 +456,6 @@ final class MqttClientImpl implements MqttClient {
return future;
}
/**
* Retrieve the MqttClient configuration
*
* @return The {@link MqttClientConfig} instance we use
*/
@Override
public MqttClientConfig getClientConfig() {
return clientConfig;
}
@Override
public void disconnect() {
if (disconnected) {
@ -480,25 +480,15 @@ final class MqttClientImpl implements MqttClient {
}
}
@Override
public void setCallback(MqttClientCallback callback) {
this.callback = callback;
}
///////////////////////////////////////////// PRIVATE API /////////////////////////////////////////////
public boolean isReconnect() {
return reconnect;
}
public void onSuccessfulReconnect() {
if (callback != null) {
callback.onSuccessfulReconnect();
}
}
ChannelFuture sendAndFlushPacket(Object message) {
if (this.channel == null) {
return null;
@ -576,7 +566,7 @@ final class MqttClientImpl implements MqttClient {
}
private void checkSubscriptions(String topic, Promise<Void> promise) {
if (!(this.subscriptions.containsKey(topic) && this.subscriptions.get(topic).size() != 0) && this.serverSubscriptions.contains(topic)) {
if (!(this.subscriptions.containsKey(topic) && !this.subscriptions.get(topic).isEmpty()) && this.serverSubscriptions.contains(topic)) {
MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttMessageIdVariableHeader variableHeader = getNewMessageId();
MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topic));
@ -614,38 +604,6 @@ final class MqttClientImpl implements MqttClient {
}
}
ConcurrentMap<Integer, MqttPendingSubscription> getPendingSubscriptions() {
return pendingSubscriptions;
}
HashMultimap<String, MqttSubscription> getSubscriptions() {
return subscriptions;
}
Set<String> getPendingSubscribeTopics() {
return pendingSubscribeTopics;
}
HashMultimap<MqttHandler, MqttSubscription> getHandlerToSubscription() {
return handlerToSubscription;
}
Set<String> getServerSubscriptions() {
return serverSubscriptions;
}
ConcurrentMap<Integer, MqttPendingUnsubscription> getPendingServerUnsubscribes() {
return pendingServerUnsubscribes;
}
ConcurrentMap<Integer, MqttPendingPublish> getPendingPublishes() {
return pendingPublishes;
}
ConcurrentMap<Integer, MqttIncomingQos2Publish> getQos2PendingIncomingPublishes() {
return qos2PendingIncomingPublishes;
}
private class MqttChannelInitializer extends ChannelInitializer<SocketChannel> {
private final Promise<MqttConnectResult> connectFuture;
@ -673,10 +631,7 @@ final class MqttClientImpl implements MqttClient {
ch.pipeline().addLast("mqttPingHandler", new MqttPingHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds()));
ch.pipeline().addLast("mqttHandler", new MqttChannelHandler(MqttClientImpl.this, connectFuture));
}
}
MqttHandler getDefaultHandler() {
return defaultHandler;
}
}

16
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttConnectResult.java

@ -17,14 +17,18 @@ package org.thingsboard.mqtt;
import io.netty.channel.ChannelFuture;
import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
import lombok.Getter;
import lombok.ToString;
@ToString
@SuppressWarnings({"WeakerAccess", "unused"})
public final class MqttConnectResult {
@Getter
private final boolean success;
@Getter
private final MqttConnectReturnCode returnCode;
@Getter
private final ChannelFuture closeFuture;
MqttConnectResult(boolean success, MqttConnectReturnCode returnCode, ChannelFuture closeFuture) {
@ -33,16 +37,4 @@ public final class MqttConnectResult {
this.closeFuture = closeFuture;
}
public boolean isSuccess() {
return success;
}
public MqttConnectReturnCode getReturnCode() {
return returnCode;
}
public ChannelFuture getCloseFuture() {
return closeFuture;
}
}

30
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttSubscription.java

@ -15,16 +15,23 @@
*/
package org.thingsboard.mqtt;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.regex.Pattern;
final class MqttSubscription {
@Getter(AccessLevel.PACKAGE)
private final String topic;
private final Pattern topicRegex;
@Getter
private final MqttHandler handler;
@Getter(AccessLevel.PACKAGE)
private final boolean once;
@Getter(AccessLevel.PACKAGE)
@Setter(AccessLevel.PACKAGE)
private volatile boolean called;
MqttSubscription(String topic, MqttHandler handler, boolean once) {
@ -40,22 +47,6 @@ final class MqttSubscription {
this.topicRegex = Pattern.compile(topic.replace("+", "[^/]+").replace("#", ".+") + "$");
}
String getTopic() {
return topic;
}
public MqttHandler getHandler() {
return handler;
}
boolean isOnce() {
return once;
}
boolean isCalled() {
return called;
}
boolean matches(String topic) {
return this.topicRegex.matcher(topic).matches();
}
@ -78,7 +69,4 @@ final class MqttSubscription {
return result;
}
void setCalled(boolean called) {
this.called = called;
}
}

2
pom.xml

@ -86,7 +86,7 @@
<zookeeper.version>3.9.3</zookeeper.version>
<protobuf.version>3.25.5</protobuf.version> <!-- A Major v4 does not support by the pubsub yet-->
<grpc.version>1.63.0</grpc.version>
<tbel.version>1.2.6</tbel.version>
<tbel.version>1.2.7</tbel.version>
<lombok.version>1.18.32</lombok.version>
<paho.client.version>1.2.5</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>

47
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java

@ -45,7 +45,6 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.net.ssl.SSLException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@ -64,12 +63,10 @@ import java.util.concurrent.TimeoutException;
)
public class TbMqttNode extends TbAbstractExternalNode {
private static final Charset UTF8 = StandardCharsets.UTF_8;
private static final String ERROR = "error";
private static final int MQTT_3_MAX_CLIENT_ID_LENGTH = 23;
private static final int MQTT_5_MAX_CLIENT_ID_LENGTH = 256;
protected TbMqttNodeConfiguration mqttNodeConfiguration;
protected MqttClient mqttClient;
@Override
@ -87,9 +84,9 @@ public class TbMqttNode extends TbAbstractExternalNode {
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg);
String topic = TbNodeUtils.processPattern(mqttNodeConfiguration.getTopicPattern(), msg);
var tbMsg = ackIfNeeded(ctx, msg);
this.mqttClient.publish(topic, Unpooled.wrappedBuffer(getData(tbMsg, mqttNodeConfiguration.isParseToPlainText()).getBytes(UTF8)),
this.mqttClient.publish(topic, Unpooled.wrappedBuffer(getData(tbMsg, mqttNodeConfiguration.isParseToPlainText()).getBytes(StandardCharsets.UTF_8)),
MqttQoS.AT_LEAST_ONCE, mqttNodeConfiguration.isRetainedMessage())
.addListener(future -> {
if (future.isSuccess()) {
@ -103,7 +100,7 @@ public class TbMqttNode extends TbAbstractExternalNode {
private TbMsg processException(TbMsg origMsg, Throwable e) {
TbMsgMetaData metaData = origMsg.getMetaData().copy();
metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage());
metaData.putValue("error", e.getClass() + ": " + e.getMessage());
return origMsg.transform()
.metaData(metaData)
.build();
@ -111,8 +108,8 @@ public class TbMqttNode extends TbAbstractExternalNode {
@Override
public void destroy() {
if (this.mqttClient != null) {
this.mqttClient.disconnect();
if (mqttClient != null) {
mqttClient.disconnect();
}
}
@ -123,11 +120,11 @@ public class TbMqttNode extends TbAbstractExternalNode {
protected MqttClient initClient(TbContext ctx) throws Exception {
MqttClientConfig config = new MqttClientConfig(getSslContext());
config.setOwnerId(getOwnerId(ctx));
if (!StringUtils.isEmpty(this.mqttNodeConfiguration.getClientId())) {
if (!StringUtils.isEmpty(mqttNodeConfiguration.getClientId())) {
config.setClientId(getClientId(ctx));
}
config.setCleanSession(this.mqttNodeConfiguration.isCleanSession());
config.setProtocolVersion(this.mqttNodeConfiguration.getProtocolVersion());
config.setCleanSession(mqttNodeConfiguration.isCleanSession());
config.setProtocolVersion(mqttNodeConfiguration.getProtocolVersion());
MqttClientSettings mqttClientSettings = ctx.getMqttClientSettings();
config.setRetransmissionConfig(new MqttClientConfig.RetransmissionConfig(
@ -139,32 +136,32 @@ public class TbMqttNode extends TbAbstractExternalNode {
prepareMqttClientConfig(config);
MqttClient client = getMqttClient(ctx, config);
client.setEventLoop(ctx.getSharedEventLoop());
Promise<MqttConnectResult> connectFuture = client.connect(this.mqttNodeConfiguration.getHost(), this.mqttNodeConfiguration.getPort());
Promise<MqttConnectResult> connectFuture = client.connect(mqttNodeConfiguration.getHost(), mqttNodeConfiguration.getPort());
MqttConnectResult result;
try {
result = connectFuture.get(this.mqttNodeConfiguration.getConnectTimeoutSec(), TimeUnit.SECONDS);
result = connectFuture.get(mqttNodeConfiguration.getConnectTimeoutSec(), TimeUnit.SECONDS);
} catch (TimeoutException ex) {
connectFuture.cancel(true);
client.disconnect();
String hostPort = this.mqttNodeConfiguration.getHost() + ":" + this.mqttNodeConfiguration.getPort();
String hostPort = mqttNodeConfiguration.getHost() + ":" + mqttNodeConfiguration.getPort();
throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s.", hostPort));
}
if (!result.isSuccess()) {
connectFuture.cancel(true);
client.disconnect();
String hostPort = this.mqttNodeConfiguration.getHost() + ":" + this.mqttNodeConfiguration.getPort();
String hostPort = mqttNodeConfiguration.getHost() + ":" + mqttNodeConfiguration.getPort();
throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s. Result code is: %s", hostPort, result.getReturnCode()));
}
return client;
}
private String getClientId(TbContext ctx) throws TbNodeException {
String clientId = this.mqttNodeConfiguration.isAppendClientIdSuffix() ?
this.mqttNodeConfiguration.getClientId() + "_" + ctx.getServiceId() :
this.mqttNodeConfiguration.getClientId();
if (clientId.length() > 23) {
throw new TbNodeException("Client ID is too long '" + clientId + "'. " +
"The length of Client ID cannot be longer than 23, but current length is " + clientId.length() + ".", true);
String clientId = mqttNodeConfiguration.isAppendClientIdSuffix() ?
mqttNodeConfiguration.getClientId() + "_" + ctx.getServiceId() :
mqttNodeConfiguration.getClientId();
int maxLength = mqttNodeConfiguration.getProtocolVersion() == MqttVersion.MQTT_3_1 ? MQTT_3_MAX_CLIENT_ID_LENGTH : MQTT_5_MAX_CLIENT_ID_LENGTH;
if (clientId.length() > maxLength) {
throw new TbNodeException("The length of Client ID cannot be longer than " + maxLength + ", but current length is " + clientId.length() + ".", true);
}
return clientId;
}
@ -174,7 +171,7 @@ public class TbMqttNode extends TbAbstractExternalNode {
}
protected void prepareMqttClientConfig(MqttClientConfig config) {
ClientCredentials credentials = this.mqttNodeConfiguration.getCredentials();
ClientCredentials credentials = mqttNodeConfiguration.getCredentials();
if (credentials.getType() == CredentialsType.BASIC) {
BasicCredentials basicCredentials = (BasicCredentials) credentials;
config.setUsername(basicCredentials.getUsername());
@ -183,7 +180,7 @@ public class TbMqttNode extends TbAbstractExternalNode {
}
private SslContext getSslContext() throws SSLException {
return this.mqttNodeConfiguration.isSsl() ? this.mqttNodeConfiguration.getCredentials().initSslContext() : null;
return mqttNodeConfiguration.isSsl() ? mqttNodeConfiguration.getCredentials().initSslContext() : null;
}
private String getData(TbMsg tbMsg, boolean parseToPlainText) {

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java

@ -146,6 +146,7 @@ public class TenantIdLoader {
tenantEntity = ctx.getNotificationRequestService().findNotificationRequestById(ctxTenantId, new NotificationRequestId(id));
break;
case NOTIFICATION:
case ADMIN_SETTINGS:
return ctxTenantId;
case NOTIFICATION_RULE:
tenantEntity = ctx.getNotificationRuleService().findNotificationRuleById(ctxTenantId, new NotificationRuleId(id));

49
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeTest.java

@ -212,40 +212,45 @@ public class TbMqttNodeTest extends AbstractRuleNodeUpgradeTest {
assertThatNoException().isThrownBy(() -> mqttNode.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(mqttNodeConfig))));
}
@Test
public void givenClientIdIsTooLong_whenInit_thenThrowsException() {
String invalidClientId = "vhfrbeb38ygwfwrgfwefgterhytjytj";
mqttNodeConfig.setClientId(invalidClientId);
@ParameterizedTest
@MethodSource("provideInvalidClientIdScenarios")
public void givenInvalidClientId_whenInit_thenThrowsException(MqttVersion version, int maxLength, int repeat, String serviceId, boolean appendSuffix) {
String baseClientId = "x".repeat(repeat);
mqttNodeConfig.setClientId(baseClientId);
mqttNodeConfig.setAppendClientIdSuffix(appendSuffix);
mqttNodeConfig.setProtocolVersion(version);
given(ctxMock.getTenantId()).willReturn(TENANT_ID);
given(ctxMock.getSelf()).willReturn(new RuleNode(RULE_NODE_ID));
String clientId = appendSuffix ? baseClientId + "_" + serviceId : baseClientId;
if (appendSuffix) {
given(ctxMock.getServiceId()).willReturn(serviceId);
}
String expectedMessage = "The length of Client ID cannot be longer than " + maxLength + ", but current length is " + clientId.length() + ".";
assertThatThrownBy(() -> mqttNode.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(mqttNodeConfig))))
.isInstanceOf(TbNodeException.class)
.hasMessage("Client ID is too long '" + invalidClientId + "'. " +
"The length of Client ID cannot be longer than 23, but current length is " + invalidClientId.length() + ".")
.hasMessage(expectedMessage)
.extracting(e -> ((TbNodeException) e).isUnrecoverable())
.isEqualTo(true);
}
@Test
public void givenClientIdIsOkAndAppendClientIdSuffixIsTrue_whenInit_thenClientIdBecomesInvalidAndThrowsException() {
String validClientId = "fertjnhnjj4ge";
mqttNodeConfig.setClientId("fertjnhnjj4ge");
mqttNodeConfig.setAppendClientIdSuffix(true);
private static Stream<Arguments> provideInvalidClientIdScenarios() {
return Stream.of(
// MQTT_5, too long clientId
Arguments.of(MqttVersion.MQTT_5, 256, 257, null, false),
given(ctxMock.getTenantId()).willReturn(TENANT_ID);
given(ctxMock.getSelf()).willReturn(new RuleNode(RULE_NODE_ID));
String serviceId = "test-service";
given(ctxMock.getServiceId()).willReturn(serviceId);
// MQTT_5, base + suffix exceeds
Arguments.of(MqttVersion.MQTT_5, 256, 250, "test-service", true),
String resultedClientId = validClientId + "_" + serviceId;
assertThatThrownBy(() -> mqttNode.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(mqttNodeConfig))))
.isInstanceOf(TbNodeException.class)
.hasMessage("Client ID is too long '" + resultedClientId + "'. " +
"The length of Client ID cannot be longer than 23, but current length is " + resultedClientId.length() + ".")
.extracting(e -> ((TbNodeException) e).isUnrecoverable())
.isEqualTo(true);
// MQTT_3_1, too long clientId
Arguments.of(MqttVersion.MQTT_3_1, 23, 24, null, false),
// MQTT_3_1, base + suffix exceeds
Arguments.of(MqttVersion.MQTT_3_1, 23, 5, "verylongservicename", true)
);
}
@Test

8
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java

@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache;
import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache;
import org.thingsboard.rule.engine.api.RuleEngineRpcService;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.ApiUsageState;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard;
@ -167,7 +168,6 @@ public class TenantIdLoaderTest {
private TenantId tenantId;
private TenantProfileId tenantProfileId;
private NotificationId notificationId;
private AbstractListeningExecutor dbExecutor;
@BeforeEach
@ -179,9 +179,8 @@ public class TenantIdLoaderTest {
}
};
dbExecutor.init();
this.tenantId = new TenantId(UUID.randomUUID());
this.tenantId = TenantId.fromUUID(UUID.randomUUID());
this.tenantProfileId = new TenantProfileId(UUID.randomUUID());
this.notificationId = new NotificationId(UUID.randomUUID());
when(ctx.getTenantId()).thenReturn(tenantId);
@ -199,6 +198,7 @@ public class TenantIdLoaderTest {
switch (entityType) {
case TENANT:
case NOTIFICATION:
case ADMIN_SETTINGS:
break;
case CUSTOMER:
Customer customer = new Customer();
@ -465,7 +465,7 @@ public class TenantIdLoaderTest {
@Test
public void test_findEntityIdAsync_other_tenant() {
checkTenant(new TenantId(UUID.randomUUID()), false);
checkTenant(TenantId.fromUUID(UUID.randomUUID()), false);
}
}

Loading…
Cancel
Save