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.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; 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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; 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.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; 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)", @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) 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')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/{key}", method = RequestMethod.GET) @GetMapping(value = "/settings/{key}")
@ResponseBody
public AdminSettings getAdminSettings( public AdminSettings getAdminSettings(
@Parameter(description = "A string value of the key (e.g. 'general' or 'mail').") @Parameter(description = "A string value of the key (e.g. 'general' or 'mail').")
@PathVariable("key") String key) throws ThingsboardException { @PathVariable("key") String key) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); 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); 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("password");
((ObjectNode) adminSettings.getJsonValue()).remove("refreshToken"); ((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. " + "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) "Referencing non-existing Administration Settings Id will cause an error." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings", method = RequestMethod.POST) @PostMapping(value = "/settings")
@ResponseBody
public AdminSettings saveAdminSettings( public AdminSettings saveAdminSettings(
@Parameter(description = "A JSON value representing the Administration Settings.") @Parameter(description = "A JSON value representing the Administration Settings.")
@RequestBody AdminSettings adminSettings) throws ThingsboardException { @RequestBody AdminSettings adminSettings) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE);
adminSettings.setTenantId(getTenantId()); adminSettings.setTenantId(getTenantId());
adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings));
if (adminSettings.getKey().equals("mail")) { if (adminSettings.getKey().equals(MAIL_SETTINGS_KEY)) {
mailService.updateMailConfiguration(); mailService.updateMailConfiguration();
((ObjectNode) adminSettings.getJsonValue()).remove("password"); ((ObjectNode) adminSettings.getJsonValue()).remove("password");
((ObjectNode) adminSettings.getJsonValue()).remove("refreshToken"); ((ObjectNode) adminSettings.getJsonValue()).remove("refreshToken");
@ -165,8 +162,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Get the Security Settings object (getSecuritySettings)", @ApiOperation(value = "Get the Security Settings object (getSecuritySettings)",
notes = "Get the Security Settings object that contains password policy, etc." + SYSTEM_AUTHORITY_PARAGRAPH) notes = "Get the Security Settings object that contains password policy, etc." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/securitySettings", method = RequestMethod.GET) @GetMapping(value = "/securitySettings")
@ResponseBody
public SecuritySettings getSecuritySettings() throws ThingsboardException { public SecuritySettings getSecuritySettings() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return checkNotNull(securitySettingsService.getSecuritySettings()); return checkNotNull(securitySettingsService.getSecuritySettings());
@ -175,8 +171,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Update Security Settings (saveSecuritySettings)", @ApiOperation(value = "Update Security Settings (saveSecuritySettings)",
notes = "Updates the Security Settings object that contains password policy, etc." + SYSTEM_AUTHORITY_PARAGRAPH) notes = "Updates the Security Settings object that contains password policy, etc." + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/securitySettings", method = RequestMethod.POST) @PostMapping(value = "/securitySettings")
@ResponseBody
public SecuritySettings saveSecuritySettings( public SecuritySettings saveSecuritySettings(
@Parameter(description = "A JSON value representing the Security Settings.") @Parameter(description = "A JSON value representing the Security Settings.")
@RequestBody SecuritySettings securitySettings) throws ThingsboardException { @RequestBody SecuritySettings securitySettings) throws ThingsboardException {
@ -188,8 +183,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Get the JWT Settings object (getJwtSettings)", @ApiOperation(value = "Get the JWT Settings object (getJwtSettings)",
notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH) notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.GET) @GetMapping(value = "/jwtSettings")
@ResponseBody
public JwtSettings getJwtSettings() throws ThingsboardException { public JwtSettings getJwtSettings() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return checkNotNull(jwtSettingsService.getJwtSettings()); return checkNotNull(jwtSettingsService.getJwtSettings());
@ -198,8 +192,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Update JWT Settings (saveJwtSettings)", @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) 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')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/jwtSettings", method = RequestMethod.POST) @PostMapping(value = "/jwtSettings")
@ResponseBody
public JwtPair saveJwtSettings( public JwtPair saveJwtSettings(
@Parameter(description = "A JSON value representing the JWT Settings.") @Parameter(description = "A JSON value representing the JWT Settings.")
@RequestBody JwtSettings jwtSettings) throws ThingsboardException { @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. " + 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) "You may change the 'To' email in the user profile of the System Administrator. " + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/testMail", method = RequestMethod.POST) @PostMapping(value = "/settings/testMail")
public void sendTestMail( public void sendTestMail(
@Parameter(description = "A JSON value representing the Mail Settings.") @Parameter(description = "A JSON value representing the Mail Settings.")
@RequestBody AdminSettings adminSettings) throws ThingsboardException { @RequestBody AdminSettings adminSettings) throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
adminSettings = checkNotNull(adminSettings); 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()) { 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"); JsonNode refreshToken = mailSettings.getJsonValue().get("refreshToken");
if (refreshToken == null) { if (refreshToken == null) {
throw new ThingsboardException("Refresh token was not generated. Please, generate refresh token.", ThingsboardErrorCode.GENERAL); 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()); settings.put("refreshToken", refreshToken.asText());
} else { } else {
if (!adminSettings.getJsonValue().has("password")) { 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()); ((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. " 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) + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/testSms", method = RequestMethod.POST) @PostMapping(value = "/settings/testSms")
public void sendTestSms( public void sendTestSms(
@Parameter(description = "A JSON value representing the Test SMS request.") @Parameter(description = "A JSON value representing the Test SMS request.")
@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { @RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException {
@ -325,7 +318,7 @@ public class AdminController extends BaseController {
notes = "Deletes the repository settings." notes = "Deletes the repository settings."
+ TENANT_AUTHORITY_PARAGRAPH) + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/repositorySettings", method = RequestMethod.DELETE) @DeleteMapping(value = "/repositorySettings")
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public DeferredResult<Void> deleteRepositorySettings() throws Exception { public DeferredResult<Void> deleteRepositorySettings() throws Exception {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE); accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE);
@ -335,7 +328,7 @@ public class AdminController extends BaseController {
@ApiOperation(value = "Check repository access (checkRepositoryAccess)", @ApiOperation(value = "Check repository access (checkRepositoryAccess)",
notes = "Attempts to check repository access. " + TENANT_AUTHORITY_PARAGRAPH) notes = "Attempts to check repository access. " + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/repositorySettings/checkAccess", method = RequestMethod.POST) @PostMapping(value = "/repositorySettings/checkAccess")
public DeferredResult<Void> checkRepositoryAccess( public DeferredResult<Void> checkRepositoryAccess(
@Parameter(description = "A JSON value representing the Repository Settings.") @Parameter(description = "A JSON value representing the Repository Settings.")
@RequestBody RepositorySettings settings) throws Exception { @RequestBody RepositorySettings settings) throws Exception {
@ -376,7 +369,7 @@ public class AdminController extends BaseController {
notes = "Deletes the auto commit settings." notes = "Deletes the auto commit settings."
+ TENANT_AUTHORITY_PARAGRAPH) + TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/autoCommitSettings", method = RequestMethod.DELETE) @DeleteMapping(value = "/autoCommitSettings")
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void deleteAutoCommitSettings() throws ThingsboardException { public void deleteAutoCommitSettings() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE); accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.DELETE);
@ -387,9 +380,8 @@ public class AdminController extends BaseController {
notes = "Check notifications about new platform releases. " notes = "Check notifications about new platform releases. "
+ SYSTEM_AUTHORITY_PARAGRAPH) + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/updates", method = RequestMethod.GET) @GetMapping(value = "/updates")
@ResponseBody public UpdateMessage checkUpdates() {
public UpdateMessage checkUpdates() throws ThingsboardException {
return updateService.checkUpdates(); return updateService.checkUpdates();
} }
@ -397,9 +389,8 @@ public class AdminController extends BaseController {
notes = "Get main information about system. " notes = "Get main information about system. "
+ SYSTEM_AUTHORITY_PARAGRAPH) + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/systemInfo", method = RequestMethod.GET) @GetMapping(value = "/systemInfo")
@ResponseBody public SystemInfo getSystemInfo() {
public SystemInfo getSystemInfo() throws ThingsboardException {
return systemInfoService.getSystemInfo(); return systemInfoService.getSystemInfo();
} }
@ -407,8 +398,7 @@ public class AdminController extends BaseController {
notes = "Get information about enabled/disabled features. " notes = "Get information about enabled/disabled features. "
+ SYSTEM_AUTHORITY_PARAGRAPH) + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/featuresInfo", method = RequestMethod.GET) @GetMapping(value = "/featuresInfo")
@ResponseBody
public FeaturesInfo getFeaturesInfo() { public FeaturesInfo getFeaturesInfo() {
return systemInfoService.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 " + "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) "further log in processing and generating access tokens. " + SYSTEM_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN')")
@RequestMapping(value = "/mail/oauth2/loginProcessingUrl", method = RequestMethod.GET) @GetMapping(value = "/mail/oauth2/loginProcessingUrl")
@ResponseBody
public String getMailProcessingUrl() throws ThingsboardException { public String getMailProcessingUrl() throws ThingsboardException {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
return "\"/api/admin/mail/oauth2/code\""; 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" + @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.)") "provider sends authorization code to specified redirect uri.)")
@PreAuthorize("hasAuthority('SYS_ADMIN')") @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 { public String getAuthorizationUrl(HttpServletRequest request, HttpServletResponse response) throws ThingsboardException {
String state = StringUtils.generateSafeToken(); String state = StringUtils.generateSafeToken();
if (request.getParameter(PREV_URI_PATH_PARAMETER) != null) { if (request.getParameter(PREV_URI_PATH_PARAMETER) != null) {
@ -452,7 +441,7 @@ public class AdminController extends BaseController {
.build() + "\""; .build() + "\"";
} }
@RequestMapping(value = "/mail/oauth2/code", params = {"code", "state"}, method = RequestMethod.GET) @GetMapping(value = "/mail/oauth2/code", params = {"code", "state"})
public void codeProcessingUrl( public void codeProcessingUrl(
@RequestParam(value = "code") String code, @RequestParam(value = "state") String state, @RequestParam(value = "code") String code, @RequestParam(value = "state") String state,
HttpServletRequest request, HttpServletResponse response) throws ThingsboardException, IOException { 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.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.commons.lang3.exception.ExceptionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource; import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
@ -64,55 +64,37 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@Service
@Slf4j @Slf4j
@Service
@RequiredArgsConstructor
public class DefaultMailService implements MailService { public class DefaultMailService implements MailService {
public static final String TARGET_EMAIL = "targetEmail"; private static final String TARGET_EMAIL = "targetEmail";
public static final String UTF_8 = "UTF-8"; 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 MessageSource messages;
private final Configuration freemarkerConfig; private final Configuration freemarkerConfig;
private final AdminSettingsService adminSettingsService; private final AdminSettingsService adminSettingsService;
private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageReportClient apiUsageClient;
private static final long DEFAULT_TIMEOUT = 10_000;
@Lazy @Lazy
@Autowired private final TbApiUsageStateService apiUsageStateService;
private TbApiUsageStateService apiUsageStateService; private final MailSenderInternalExecutorService mailExecutorService;
private final PasswordResetExecutorService passwordResetExecutorService;
@Autowired private final TbMailContextComponent ctx;
private MailSenderInternalExecutorService mailExecutorService; private final RateLimitService rateLimitService;
@Autowired
private PasswordResetExecutorService passwordResetExecutorService;
@Autowired
private TbMailContextComponent ctx;
@Autowired
private RateLimitService rateLimitService;
@Value("${mail.per_tenant_rate_limits:}") @Value("${mail.per_tenant_rate_limits:}")
private String perTenantRateLimitConfig; private String perTenantRateLimitConfig;
private final ScheduledExecutorService timeoutScheduler;
private TbMailSender mailSender; private TbMailSender mailSender;
private String mailFrom; private String mailFrom;
private long timeout; 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 @PostConstruct
private void init() { private void init() {
updateMailConfiguration(); updateMailConfiguration();
@ -120,9 +102,7 @@ public class DefaultMailService implements MailService {
@PreDestroy @PreDestroy
public void destroy() { public void destroy() {
if (timeoutScheduler != null) { timeoutScheduler.shutdownNow();
timeoutScheduler.shutdownNow();
}
} }
@Override @Override
@ -311,22 +291,21 @@ public class DefaultMailService implements MailService {
model.put("apiFeature", apiFeature.getLabel()); model.put("apiFeature", apiFeature.getLabel());
model.put(TARGET_EMAIL, email); model.put(TARGET_EMAIL, email);
String message = null; String message = switch (stateValue) {
case ENABLED -> {
switch (stateValue) {
case ENABLED:
model.put("apiLabel", toEnabledValueLabel(apiFeature)); model.put("apiLabel", toEnabledValueLabel(apiFeature));
message = mergeTemplateIntoString("state.enabled.ftl", model); yield mergeTemplateIntoString("state.enabled.ftl", model);
break; }
case WARNING: case WARNING -> {
model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(recordState)); model.put("apiValueLabel", toDisabledValueLabel(apiFeature) + " " + toWarningValueLabel(recordState));
message = mergeTemplateIntoString("state.warning.ftl", model); yield mergeTemplateIntoString("state.warning.ftl", model);
break; }
case DISABLED: case DISABLED -> {
model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(recordState)); model.put("apiLimitValueLabel", toDisabledValueLabel(apiFeature) + " " + toDisabledValueLabel(recordState));
message = mergeTemplateIntoString("state.disabled.ftl", model); yield mergeTemplateIntoString("state.disabled.ftl", model);
break; }
} };
sendMail(mailSender, mailFrom, email, subject, message, timeout); sendMail(mailSender, mailFrom, email, subject, message, timeout);
} }
@ -341,89 +320,55 @@ public class DefaultMailService implements MailService {
} }
private String toEnabledValueLabel(ApiFeature apiFeature) { private String toEnabledValueLabel(ApiFeature apiFeature) {
switch (apiFeature) { return switch (apiFeature) {
case DB: case DB -> "save";
return "save"; case TRANSPORT -> "receive";
case TRANSPORT: case JS -> "invoke";
return "receive"; case RE -> "process";
case JS: case EMAIL, SMS -> "send";
return "invoke"; case ALARM -> "create";
case RE: default -> throw new RuntimeException("Not implemented!");
return "process"; };
case EMAIL:
case SMS:
return "send";
case ALARM:
return "create";
default:
throw new RuntimeException("Not implemented!");
}
} }
private String toDisabledValueLabel(ApiFeature apiFeature) { private String toDisabledValueLabel(ApiFeature apiFeature) {
switch (apiFeature) { return switch (apiFeature) {
case DB: case DB -> "saved";
return "saved"; case TRANSPORT -> "received";
case TRANSPORT: case JS -> "invoked";
return "received"; case RE -> "processed";
case JS: case EMAIL, SMS -> "sent";
return "invoked"; case ALARM -> "created";
case RE: default -> throw new RuntimeException("Not implemented!");
return "processed"; };
case EMAIL:
case SMS:
return "sent";
case ALARM:
return "created";
default:
throw new RuntimeException("Not implemented!");
}
} }
private String toWarningValueLabel(ApiUsageRecordState recordState) { private String toWarningValueLabel(ApiUsageRecordState recordState) {
String valueInM = recordState.getValueAsString(); String valueInM = recordState.getValueAsString();
String thresholdInM = recordState.getThresholdAsString(); String thresholdInM = recordState.getThresholdAsString();
switch (recordState.getKey()) { return switch (recordState.getKey()) {
case STORAGE_DP_COUNT: case STORAGE_DP_COUNT, TRANSPORT_DP_COUNT -> valueInM + " out of " + thresholdInM + " allowed data points";
case TRANSPORT_DP_COUNT: case TRANSPORT_MSG_COUNT -> valueInM + " out of " + thresholdInM + " allowed messages";
return valueInM + " out of " + thresholdInM + " allowed data points"; case JS_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed JavaScript functions";
case TRANSPORT_MSG_COUNT: case TBEL_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed Tbel functions";
return valueInM + " out of " + thresholdInM + " allowed messages"; case RE_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed Rule Engine messages";
case JS_EXEC_COUNT: case EMAIL_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed Email messages";
return valueInM + " out of " + thresholdInM + " allowed JavaScript functions"; case SMS_EXEC_COUNT -> valueInM + " out of " + thresholdInM + " allowed SMS messages";
case TBEL_EXEC_COUNT: default -> throw new RuntimeException("Not implemented!");
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!");
}
} }
private String toDisabledValueLabel(ApiUsageRecordState recordState) { private String toDisabledValueLabel(ApiUsageRecordState recordState) {
switch (recordState.getKey()) { return switch (recordState.getKey()) {
case STORAGE_DP_COUNT: case STORAGE_DP_COUNT, TRANSPORT_DP_COUNT -> recordState.getValueAsString() + " data points";
case TRANSPORT_DP_COUNT: case TRANSPORT_MSG_COUNT -> recordState.getValueAsString() + " messages";
return recordState.getValueAsString() + " data points"; case JS_EXEC_COUNT -> "JavaScript functions " + recordState.getValueAsString() + " times";
case TRANSPORT_MSG_COUNT: case TBEL_EXEC_COUNT -> "TBEL functions " + recordState.getValueAsString() + " times";
return recordState.getValueAsString() + " messages"; case RE_EXEC_COUNT -> recordState.getValueAsString() + " Rule Engine messages";
case JS_EXEC_COUNT: case EMAIL_EXEC_COUNT -> recordState.getValueAsString() + " Email messages";
return "JavaScript functions " + recordState.getValueAsString() + " times"; case SMS_EXEC_COUNT -> recordState.getValueAsString() + " SMS messages";
case TBEL_EXEC_COUNT: default -> throw new RuntimeException("Not implemented!");
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!");
}
} }
private void sendMail(JavaMailSenderImpl mailSender, String mailFrom, String email, 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 com.google.api.client.json.gson.GsonFactory;
import jakarta.mail.MessagingException; import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage; import jakarta.mail.internet.MimeMessage;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.mail.MailException; import org.springframework.mail.MailException;
@ -50,8 +51,10 @@ public class TbMailSender extends JavaMailSenderImpl {
private final TbMailContextComponent ctx; private final TbMailContextComponent ctx;
private final Lock lock; private final Lock lock;
@Getter
private final Boolean oauth2Enabled; private final Boolean oauth2Enabled;
private volatile String accessToken; private volatile String accessToken;
@Getter
private volatile long tokenExpires; private volatile long tokenExpires;
public TbMailSender(TbMailContextComponent ctx, JsonNode jsonConfig) { public TbMailSender(TbMailContextComponent ctx, JsonNode jsonConfig) {
@ -70,14 +73,6 @@ public class TbMailSender extends JavaMailSenderImpl {
setJavaMailProperties(createJavaMailProperties(jsonConfig)); setJavaMailProperties(createJavaMailProperties(jsonConfig));
} }
public Boolean getOauth2Enabled() {
return oauth2Enabled;
}
public long getTokenExpires() {
return tokenExpires;
}
@Override @Override
protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException { protected void doSend(MimeMessage[] mimeMessages, @Nullable Object[] originalMessages) throws MailException {
updateOauth2PasswordIfExpired(); updateOauth2PasswordIfExpired();
@ -98,8 +93,8 @@ public class TbMailSender extends JavaMailSenderImpl {
super.testConnection(); super.testConnection();
} }
public void updateOauth2PasswordIfExpired() { public void updateOauth2PasswordIfExpired() {
if (getOauth2Enabled() && (System.currentTimeMillis() > getTokenExpires())){ if (getOauth2Enabled() && (System.currentTimeMillis() > getTokenExpires())) {
refreshAccessToken(); refreshAccessToken();
setPassword(accessToken); setPassword(accessToken);
} }
@ -168,8 +163,8 @@ public class TbMailSender extends JavaMailSenderImpl {
.setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret)) .setClientAuthentication(new ClientParametersAuthentication(clientId, clientSecret))
.execute(); .execute();
if (MailOauth2Provider.OFFICE_365.name().equals(providerId)) { if (MailOauth2Provider.OFFICE_365.name().equals(providerId)) {
((ObjectNode)jsonValue).put("refreshToken", tokenResponse.getRefreshToken()); ((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("refreshTokenExpires", Instant.now().plus(Duration.ofDays(AZURE_DEFAULT_REFRESH_TOKEN_LIFETIME_IN_DAYS)).toEpochMilli());
ctx.getAdminSettingsService().saveAdminSettings(TenantId.SYS_TENANT_ID, settings); ctx.getAdminSettingsService().saveAdminSettings(TenantId.SYS_TENANT_ID, settings);
} }
accessToken = tokenResponse.getAccessToken(); accessToken = tokenResponse.getAccessToken();
@ -190,4 +185,5 @@ public class TbMailSender extends JavaMailSenderImpl {
throw new IncorrectParameterException(String.format("Invalid smtp port value: %s", strPort)); 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="; errorPrefix = "/login?loginError=";
} }
getRedirectStrategy().sendRedirect(request, response, baseUrl + errorPrefix + 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(); 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; import java.util.Set;
public enum Resource { public enum Resource {
ADMIN_SETTINGS(), ADMIN_SETTINGS(EntityType.ADMIN_SETTINGS),
ALARM(EntityType.ALARM), ALARM(EntityType.ALARM),
DEVICE(EntityType.DEVICE), DEVICE(EntityType.DEVICE),
ASSET(EntityType.ASSET), 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 @Override
public SmsSender createSmsSender(SmsProviderConfiguration config) { public SmsSender createSmsSender(SmsProviderConfiguration config) {
switch (config.getType()) { return switch (config.getType()) {
case AWS_SNS: case AWS_SNS -> new AwsSmsSender((AwsSnsSmsProviderConfiguration) config);
return new AwsSmsSender((AwsSnsSmsProviderConfiguration)config); case TWILIO -> new TwilioSmsSender((TwilioSmsProviderConfiguration) config);
case TWILIO: case SMPP -> new SmppSmsSender((SmppSmsProviderConfiguration) config);
return new TwilioSmsSender((TwilioSmsProviderConfiguration)config); default -> throw new RuntimeException("Unknown SMS provider type " + config.getType());
case SMPP: };
return 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 com.fasterxml.jackson.databind.JsonNode;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy; import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.core.NestedRuntimeException; import org.springframework.core.NestedRuntimeException;
import org.springframework.stereotype.Service; 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.dao.settings.AdminSettingsService;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
@Service
@Slf4j @Slf4j
@Service
@RequiredArgsConstructor
public class DefaultSmsService implements SmsService { public class DefaultSmsService implements SmsService {
private final SmsSenderFactory smsSenderFactory; private final SmsSenderFactory smsSenderFactory;
@ -48,13 +50,6 @@ public class DefaultSmsService implements SmsService {
private SmsSender smsSender; 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 @PostConstruct
private void init() { private void init() {
updateSmsConfiguration(); updateSmsConfiguration();
@ -148,4 +143,5 @@ public class DefaultSmsService implements SmsService {
return new ThingsboardException(String.format("Unable to send SMS: %s", message), return new ThingsboardException(String.format("Unable to send SMS: %s", message),
ThingsboardErrorCode.GENERAL); 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.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -750,6 +752,284 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected, actual); 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 @Test
public void arraysWillCauseArrayIndexOutOfBoundsException_Test() throws ExecutionException, InterruptedException { public void arraysWillCauseArrayIndexOutOfBoundsException_Test() throws ExecutionException, InterruptedException {
msgStr = """ msgStr = """
@ -2399,25 +2679,19 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
list.add(0x35); list.add(0x35);
return isList(list); 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); Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual); assertInstanceOf(Boolean.class, actual);
assertTrue((Boolean) 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 @Test
@ -2435,16 +2709,6 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
Object actual = invokeScript(evalScript(decoderStr), msgStr); Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual); assertInstanceOf(Boolean.class, actual);
assertTrue((Boolean) 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 @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 com.google.gson.JsonElement;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.eclipse.leshan.client.LeshanClient;
import org.eclipse.leshan.client.object.Security; 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.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.eclipse.leshan.server.registration.Registration;
import org.junit.After; import org.junit.After;
import org.junit.Assert; 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.AbstractTransportIntegrationTest;
import org.thingsboard.server.transport.lwm2m.client.LwM2MTestClient; 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.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.DefaultLwM2mUplinkMsgHandler;
import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler; import org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mUplinkMsgHandler;
@ -82,6 +89,7 @@ import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; 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.assertNotNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@ -351,7 +360,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
getWsClient().waitForReply(); getWsClient().waitForReply();
getWsClient().registerWaitForUpdate(); 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()); awaitObserveReadAll(1, lwM2MTestClient.getDeviceIdStr());
String msg = getWsClient().waitForUpdate(); String msg = getWsClient().waitForUpdate();
@ -422,7 +431,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
getWsClient().waitForReply(); getWsClient().waitForReply();
getWsClient().registerWaitForUpdate(); 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()); awaitObserveReadAll(cntObserve, lwM2MTestClient.getDeviceIdStr());
String msg = getWsClient().waitForUpdate(); String msg = getWsClient().waitForUpdate();
@ -543,16 +552,17 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
public void createNewClient(Security security, Security securityBs, boolean isRpc, public void createNewClient(Security security, Security securityBs, boolean isRpc,
String endpoint, String deviceIdStr) throws Exception { 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, public void createNewClient(Security security, Security securityBs, boolean isRpc,
String endpoint, Integer clientDtlsCidLength, String deviceIdStr) throws Exception { 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, 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); this.clientDestroy(false);
lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint, resources); lwM2MTestClient = new LwM2MTestClient(this.executor, endpoint, resources);
@ -560,11 +570,86 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractTransportInte
int clientPort = socket.getLocalPort(); int clientPort = socket.getLocalPort();
lwM2MTestClient.init(security, securityBs, clientPort, isRpc, lwM2MTestClient.init(security, securityBs, clientPort, isRpc,
this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest, this.defaultLwM2mUplinkMsgHandlerTest, this.clientContextTest,
clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR); clientDtlsCidLength, queueMode, supportFormatOnly_SenMLJSON_SenMLCBOR, value3_0_9);
} }
lwM2MTestClient.setDeviceIdStr(deviceIdStr); 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) { private void clientDestroy(boolean isAfter) {
try { try {
if (lwM2MTestClient != null && lwM2MTestClient.getLeshanClient() != null) { 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, public void init(Security security, Security securityBs, int port, boolean isRpc,
LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler, LwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandler,
LwM2mClientContext clientContext, Integer cIdLength, boolean queueMode, 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); Assert.assertNull("client already initialized", leshanClient);
this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler; this.defaultLwM2mUplinkMsgHandlerTest = defaultLwM2mUplinkMsgHandler;
this.clientContext = clientContext; this.clientContext = clientContext;
@ -197,7 +197,7 @@ public class LwM2MTestClient {
initializer.setInstancesForObject(SERVER, lwm2mServer); 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(FIRMWARE, fwLwM2MDevice = new FwLwM2MDevice());
initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice()); initializer.setInstancesForObject(SOFTWARE_MANAGEMENT, swLwM2MDevice = new SwLwM2MDevice());
initializer.setClassForObject(ACCESS_CONTROL, DummyInstanceEnabler.class); 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 = private static Map<Integer, Long> errorCode =
Map.of(0, 0L); // 0-32 Map.of(0, 0L); // 0-32
private Integer value3_0_9;
public SimpleLwM2MDevice() { public SimpleLwM2MDevice() {
} }
public SimpleLwM2MDevice(ScheduledExecutorService executorService) { public SimpleLwM2MDevice(ScheduledExecutorService executorService, Integer value3_0_9) {
this.value3_0_9 = value3_0_9;
try { try {
executorService.scheduleWithFixedDelay(() -> { if ( this.value3_0_9 == null) {
fireResourceChange(9); executorService.scheduleWithFixedDelay(() -> {
fireResourceChange(20); fireResourceChange(9);
} fireResourceChange(20);
, 1, 1, TimeUnit.SECONDS); // 2 sec }
, 1, 1, TimeUnit.SECONDS); // 2 sec
// , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN // , 1800000, 1800000, TimeUnit.MILLISECONDS); // 30 MIN
}
} catch (Throwable e) { } catch (Throwable e) {
log.error("[{}]Throwable", e.toString()); log.error("[{}]Throwable", e.toString());
e.printStackTrace(); e.printStackTrace();
@ -211,8 +215,14 @@ public class SimpleLwM2MDevice extends BaseInstanceEnabler implements Destroyabl
} }
private int getBatteryLevel() { private int getBatteryLevel() {
int valBattery = randomIterator.nextInt(); int valBattery;
log.trace("Send from client [3/0/9] val: [{}]", 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; 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.AdminSettings;
import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.AdminSettingsId;
import org.thingsboard.server.common.data.id.TenantId; 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); AdminSettings findAdminSettingsById(TenantId tenantId, AdminSettingsId adminSettingsId);
@ -31,6 +32,4 @@ public interface AdminSettingsService {
boolean deleteAdminSettingsByTenantIdAndKey(TenantId tenantId, String key); 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; package org.thingsboard.server.common.data;
/**
* @author Andrew Shvayka
*/
public class DataConstants { public class DataConstants {
public static final String TENANT = "TENANT"; 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.EnumSet;
import java.util.List; import java.util.List;
/**
* @author Andrew Shvayka
*/
public enum EntityType { public enum EntityType {
TENANT(1), TENANT(1),
CUSTOMER(2), CUSTOMER(2),
@ -65,7 +62,8 @@ public enum EntityType {
MOBILE_APP_BUNDLE(38), MOBILE_APP_BUNDLE(38),
CALCULATED_FIELD(39), CALCULATED_FIELD(39),
CALCULATED_FIELD_LINK(40), CALCULATED_FIELD_LINK(40),
JOB(41); JOB(41),
ADMIN_SETTINGS(42);
@Getter @Getter
private final int protoNumber; // Corresponds to EntityTypeProto 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.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty; 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; import java.util.UUID;
public class AdminSettingsId extends UUIDBased { public class AdminSettingsId extends UUIDBased implements EntityId {
@Serial
private static final long serialVersionUID = -4208011957475806567L;
@JsonCreator @JsonCreator
public AdminSettingsId(@JsonProperty("id") UUID id){ public AdminSettingsId(@JsonProperty("id") UUID id) {
super(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.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.io.Serial;
import java.util.UUID; import java.util.UUID;
public class EdgeId extends UUIDBased implements EntityId { public class EdgeId extends UUIDBased implements EntityId {
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@JsonIgnore @JsonIgnore
@ -51,4 +53,5 @@ public class EdgeId extends UUIDBased implements EntityId {
public static EdgeId fromUUID(@JsonProperty("id") UUID id) { public static EdgeId fromUUID(@JsonProperty("id") UUID id) {
return edges.computeIfAbsent(id, EdgeId::new); 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.io.Serializable;
import java.util.UUID; import java.util.UUID;
/**
* @author Andrew Shvayka
*/
@JsonDeserialize(using = EntityIdDeserializer.class) @JsonDeserialize(using = EntityIdDeserializer.class)
@JsonSerialize(using = EntityIdSerializer.class) @JsonSerialize(using = EntityIdSerializer.class)
@Schema @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; import java.util.UUID;
/**
* Created by ashvayka on 25.04.17.
*/
public class EntityIdFactory { public class EntityIdFactory {
public static EntityId getByTypeAndUuid(int type, String uuid) { public static EntityId getByTypeAndUuid(int type, String uuid) {
@ -50,131 +47,74 @@ public class EntityIdFactory {
} }
public static EntityId getByTypeAndUuid(EntityType type, UUID uuid) { public static EntityId getByTypeAndUuid(EntityType type, UUID uuid) {
switch (type) { return switch (type) {
case TENANT: case TENANT -> TenantId.fromUUID(uuid);
return TenantId.fromUUID(uuid); case CUSTOMER -> new CustomerId(uuid);
case CUSTOMER: case USER -> new UserId(uuid);
return new CustomerId(uuid); case DASHBOARD -> new DashboardId(uuid);
case USER: case DEVICE -> new DeviceId(uuid);
return new UserId(uuid); case ASSET -> new AssetId(uuid);
case DASHBOARD: case ALARM -> new AlarmId(uuid);
return new DashboardId(uuid); case RULE_CHAIN -> new RuleChainId(uuid);
case DEVICE: case RULE_NODE -> new RuleNodeId(uuid);
return new DeviceId(uuid); case ENTITY_VIEW -> new EntityViewId(uuid);
case ASSET: case WIDGETS_BUNDLE -> new WidgetsBundleId(uuid);
return new AssetId(uuid); case WIDGET_TYPE -> new WidgetTypeId(uuid);
case ALARM: case DEVICE_PROFILE -> new DeviceProfileId(uuid);
return new AlarmId(uuid); case ASSET_PROFILE -> new AssetProfileId(uuid);
case RULE_CHAIN: case TENANT_PROFILE -> new TenantProfileId(uuid);
return new RuleChainId(uuid); case API_USAGE_STATE -> new ApiUsageStateId(uuid);
case RULE_NODE: case TB_RESOURCE -> new TbResourceId(uuid);
return new RuleNodeId(uuid); case OTA_PACKAGE -> new OtaPackageId(uuid);
case ENTITY_VIEW: case EDGE -> EdgeId.fromUUID(uuid);
return new EntityViewId(uuid); case RPC -> new RpcId(uuid);
case WIDGETS_BUNDLE: case QUEUE -> new QueueId(uuid);
return new WidgetsBundleId(uuid); case NOTIFICATION_TARGET -> new NotificationTargetId(uuid);
case WIDGET_TYPE: case NOTIFICATION_REQUEST -> new NotificationRequestId(uuid);
return new WidgetTypeId(uuid); case NOTIFICATION_RULE -> new NotificationRuleId(uuid);
case DEVICE_PROFILE: case NOTIFICATION_TEMPLATE -> new NotificationTemplateId(uuid);
return new DeviceProfileId(uuid); case NOTIFICATION -> new NotificationId(uuid);
case ASSET_PROFILE: case QUEUE_STATS -> new QueueStatsId(uuid);
return new AssetProfileId(uuid); case OAUTH2_CLIENT -> new OAuth2ClientId(uuid);
case TENANT_PROFILE: case MOBILE_APP -> new MobileAppId(uuid);
return new TenantProfileId(uuid); case DOMAIN -> new DomainId(uuid);
case API_USAGE_STATE: case MOBILE_APP_BUNDLE -> new MobileAppBundleId(uuid);
return new ApiUsageStateId(uuid); case CALCULATED_FIELD -> new CalculatedFieldId(uuid);
case TB_RESOURCE: case CALCULATED_FIELD_LINK -> new CalculatedFieldLinkId(uuid);
return new TbResourceId(uuid); case JOB -> new JobId(uuid);
case OTA_PACKAGE: case ADMIN_SETTINGS -> new AdminSettingsId(uuid);
return new OtaPackageId(uuid); default -> throw new IllegalArgumentException("EntityType " + type + " is not supported!");
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!");
} }
public static EntityId getByEdgeEventTypeAndUuid(EdgeEventType edgeEventType, UUID uuid) { public static EntityId getByEdgeEventTypeAndUuid(EdgeEventType edgeEventType, UUID uuid) {
switch (edgeEventType) { return switch (edgeEventType) {
case TENANT: case TENANT -> TenantId.fromUUID(uuid);
return TenantId.fromUUID(uuid); case CUSTOMER -> new CustomerId(uuid);
case CUSTOMER: case USER -> new UserId(uuid);
return new CustomerId(uuid); case DASHBOARD -> new DashboardId(uuid);
case USER: case DEVICE -> new DeviceId(uuid);
return new UserId(uuid); case ASSET -> new AssetId(uuid);
case DASHBOARD: case ALARM -> new AlarmId(uuid);
return new DashboardId(uuid); case RULE_CHAIN -> new RuleChainId(uuid);
case DEVICE: case ENTITY_VIEW -> new EntityViewId(uuid);
return new DeviceId(uuid); case WIDGETS_BUNDLE -> new WidgetsBundleId(uuid);
case ASSET: case WIDGET_TYPE -> new WidgetTypeId(uuid);
return new AssetId(uuid); case DEVICE_PROFILE -> new DeviceProfileId(uuid);
case ALARM: case ASSET_PROFILE -> new AssetProfileId(uuid);
return new AlarmId(uuid); case TENANT_PROFILE -> new TenantProfileId(uuid);
case RULE_CHAIN: case OTA_PACKAGE -> new OtaPackageId(uuid);
return new RuleChainId(uuid); case EDGE -> EdgeId.fromUUID(uuid);
case ENTITY_VIEW: case QUEUE -> new QueueId(uuid);
return new EntityViewId(uuid); case TB_RESOURCE -> new TbResourceId(uuid);
case WIDGETS_BUNDLE: case NOTIFICATION_RULE -> new NotificationRuleId(uuid);
return new WidgetsBundleId(uuid); case NOTIFICATION_TARGET -> new NotificationTargetId(uuid);
case WIDGET_TYPE: case NOTIFICATION_TEMPLATE -> new NotificationTemplateId(uuid);
return new WidgetTypeId(uuid); case OAUTH2_CLIENT -> new OAuth2ClientId(uuid);
case DEVICE_PROFILE: case DOMAIN -> new DomainId(uuid);
return new DeviceProfileId(uuid); case CALCULATED_FIELD -> new CalculatedFieldId(uuid);
case ASSET_PROFILE: default -> throw new IllegalArgumentException("EdgeEventType " + edgeEventType + " is not supported!");
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!");
} }
} }

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 io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.io.Serial;
import java.util.UUID; import java.util.UUID;
public class JobId extends UUIDBased implements EntityId { public class JobId extends UUIDBased implements EntityId {
@Serial
private static final long serialVersionUID = -2225072123132918395L;
@JsonCreator @JsonCreator
public JobId(@JsonProperty("id") UUID id) { public JobId(@JsonProperty("id") UUID id) {
super(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.springframework.util.ConcurrentReferenceHashMap.ReferenceType;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.io.Serial;
import java.util.UUID; import java.util.UUID;
public final class TenantId extends UUIDBased implements EntityId { public final class TenantId extends UUIDBased implements EntityId {
@ -33,6 +34,7 @@ public final class TenantId extends UUIDBased implements EntityId {
@JsonIgnore @JsonIgnore
public static final TenantId SYS_TENANT_ID = TenantId.fromUUID(EntityId.NULL_UUID); public static final TenantId SYS_TENANT_ID = TenantId.fromUUID(EntityId.NULL_UUID);
@Serial
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@JsonCreator @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) { public static EdgeEvent fromProto(TransportProtos.EdgeEventMsgProto proto) {
EdgeEvent edgeEvent = new EdgeEvent(); 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.setTenantId(tenantId);
edgeEvent.setType(EdgeEventType.valueOf(proto.getEntityType())); edgeEvent.setType(EdgeEventType.valueOf(proto.getEntityType()));
edgeEvent.setAction(EdgeEventActionType.valueOf(proto.getAction())); edgeEvent.setAction(EdgeEventActionType.valueOf(proto.getAction()));
@ -845,7 +845,7 @@ public class ProtoUtils {
public static Device fromProto(TransportProtos.DeviceProto proto) { public static Device fromProto(TransportProtos.DeviceProto proto) {
Device device = new Device(getEntityId(proto.getDeviceIdMSB(), proto.getDeviceIdLSB(), DeviceId::new)); Device device = new Device(getEntityId(proto.getDeviceIdMSB(), proto.getDeviceIdLSB(), DeviceId::new));
device.setCreatedTime(proto.getCreatedTime()); 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.setName(proto.getDeviceName());
device.setType(proto.getDeviceType()); device.setType(proto.getDeviceType());
device.setDeviceProfileId(getEntityId(proto.getDeviceProfileIdMSB(), proto.getDeviceProfileIdLSB(), DeviceProfileId::new)); device.setDeviceProfileId(getEntityId(proto.getDeviceProfileIdMSB(), proto.getDeviceProfileIdLSB(), DeviceProfileId::new));
@ -937,7 +937,7 @@ public class ProtoUtils {
public static DeviceProfile fromProto(TransportProtos.DeviceProfileProto proto) { public static DeviceProfile fromProto(TransportProtos.DeviceProfileProto proto) {
DeviceProfile deviceProfile = new DeviceProfile(getEntityId(proto.getDeviceProfileIdMSB(), proto.getDeviceProfileIdLSB(), DeviceProfileId::new)); DeviceProfile deviceProfile = new DeviceProfile(getEntityId(proto.getDeviceProfileIdMSB(), proto.getDeviceProfileIdLSB(), DeviceProfileId::new));
deviceProfile.setCreatedTime(proto.getCreatedTime()); 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.setName(proto.getName());
deviceProfile.setDefault(proto.getIsDefault()); deviceProfile.setDefault(proto.getIsDefault());
deviceProfile.setType(DeviceProfileType.valueOf(proto.getType())); deviceProfile.setType(DeviceProfileType.valueOf(proto.getType()));
@ -1028,7 +1028,7 @@ public class ProtoUtils {
} }
public static Tenant fromProto(TransportProtos.TenantProto proto) { 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.setCreatedTime(proto.getCreatedTime());
tenant.setTenantProfileId(getEntityId(proto.getTenantProfileIdMSB(), proto.getTenantProfileIdLSB(), TenantProfileId::new)); tenant.setTenantProfileId(getEntityId(proto.getTenantProfileIdMSB(), proto.getTenantProfileIdLSB(), TenantProfileId::new));
tenant.setTitle(proto.getTitle()); tenant.setTitle(proto.getTitle());
@ -1142,7 +1142,7 @@ public class ProtoUtils {
public static TbResource fromProto(TransportProtos.TbResourceProto proto) { public static TbResource fromProto(TransportProtos.TbResourceProto proto) {
TbResource resource = new TbResource(getEntityId(proto.getResourceIdMSB(), proto.getResourceIdLSB(), TbResourceId::new)); 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.setCreatedTime(proto.getCreatedTime());
resource.setTitle(proto.getTitle()); resource.setTitle(proto.getTitle());
resource.setResourceType(ResourceType.valueOf(proto.getResourceType())); resource.setResourceType(ResourceType.valueOf(proto.getResourceType()));
@ -1198,7 +1198,7 @@ public class ProtoUtils {
public static ApiUsageState fromProto(TransportProtos.ApiUsageStateProto proto) { public static ApiUsageState fromProto(TransportProtos.ApiUsageStateProto proto) {
ApiUsageState apiUsageState = new ApiUsageState(getEntityId(proto.getApiUsageStateIdMSB(), proto.getApiUsageStateIdLSB(), ApiUsageStateId::new)); 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.setCreatedTime(proto.getCreatedTime());
apiUsageState.setEntityId(EntityIdFactory.getByTypeAndUuid(fromProto(proto.getEntityType()), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB()))); apiUsageState.setEntityId(EntityIdFactory.getByTypeAndUuid(fromProto(proto.getEntityType()), new UUID(proto.getEntityIdMSB(), proto.getEntityIdLSB())));
apiUsageState.setTransportState(ApiUsageStateValue.valueOf(proto.getTransportState())); 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 = 39;
CALCULATED_FIELD_LINK = 40; CALCULATED_FIELD_LINK = 40;
JOB = 41; JOB = 41;
ADMIN_SETTINGS = 42;
} }
enum ApiUsageRecordKeyProto { 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.ParserConfiguration;
import org.mvel2.execution.ExecutionArrayList; import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap; import org.mvel2.execution.ExecutionHashMap;
import org.mvel2.execution.ExecutionLinkedHashSet;
import org.mvel2.util.MethodStub; import org.mvel2.util.MethodStub;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.geo.Coordinates; import org.thingsboard.common.util.geo.Coordinates;
@ -46,6 +47,7 @@ import java.util.Base64;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -386,6 +388,12 @@ public class TbUtils {
Object.class))); Object.class)));
parserConfig.addImport("isArray", new MethodStub(TbUtils.class.getMethod("isArray", parserConfig.addImport("isArray", new MethodStub(TbUtils.class.getMethod("isArray",
Object.class))); 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) { public static String btoa(String input) {
@ -1481,6 +1489,19 @@ public class TbUtils {
return obj != null && obj.getClass().isArray(); 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) { private static byte isValidIntegerToByte(Integer val) {
if (val > 255 || val < -128) { if (val > 255 || val < -128) {
throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " + 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.SandboxedParserConfiguration;
import org.mvel2.execution.ExecutionArrayList; import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap; import org.mvel2.execution.ExecutionHashMap;
import org.mvel2.execution.ExecutionLinkedHashSet;
import java.io.IOException; import java.io.IOException;
import java.math.BigDecimal; import java.math.BigDecimal;
@ -39,14 +40,18 @@ import java.util.Base64;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import static java.lang.Character.MAX_RADIX; import static java.lang.Character.MAX_RADIX;
import static java.lang.Character.MIN_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.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
@Slf4j @Slf4j
@ -1184,10 +1189,11 @@ public class TbUtilsTest {
@Test @Test
public void isList() throws ExecutionException, InterruptedException { public void isList() throws ExecutionException, InterruptedException {
List<Integer> liat = List.of(0x35); List<Integer> list = List.of(0x35);
assertTrue(TbUtils.isList(liat)); assertTrue(TbUtils.isList(list));
assertFalse(TbUtils.isMap(liat)); assertFalse(TbUtils.isMap(list));
assertFalse(TbUtils.isArray(liat)); assertFalse(TbUtils.isArray(list));
assertFalse(TbUtils.isSet(list));
} }
@Test @Test
@ -1195,6 +1201,52 @@ public class TbUtilsTest {
byte [] array = new byte[]{1, 2, 3}; byte [] array = new byte[]{1, 2, 3};
assertTrue(TbUtils.isArray(array)); assertTrue(TbUtils.isArray(array));
assertFalse(TbUtils.isList(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) { private static List<Byte> toList(byte[] data) {
@ -1204,5 +1256,13 @@ public class TbUtilsTest {
} }
return result; 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(); LwM2mPath path = instant.getKey();
LwM2mNode node = instant.getValue(); LwM2mNode node = instant.getValue();
LwM2mClient lwM2MClient = clientContext.getClientByEndpoint(registration.getEndpoint()); 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) { if (objectModelVersion != null) {
ResourceUpdateResult updateResource = new ResourceUpdateResult(lwM2MClient); ResourceUpdateResult updateResource = new ResourceUpdateResult(lwM2MClient);
if (node instanceof LwM2mObject) { 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 @Component
public class HybridClientRegistrationRepository implements ClientRegistrationRepository { public class HybridClientRegistrationRepository implements ClientRegistrationRepository {
private static final String defaultRedirectUriTemplate = "{baseUrl}/login/oauth2/code/{registrationId}"; private static final String defaultRedirectUriTemplate = "{baseUrl}/login/oauth2/code/{registrationId}";
@Autowired @Autowired
@ -37,11 +38,13 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep
@Override @Override
public ClientRegistration findByRegistrationId(String registrationId) { public ClientRegistration findByRegistrationId(String registrationId) {
OAuth2Client oAuth2Client = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2ClientId(UUID.fromString(registrationId))); OAuth2Client oAuth2Client = oAuth2ClientService.findOAuth2ClientById(TenantId.SYS_TENANT_ID, new OAuth2ClientId(UUID.fromString(registrationId)));
return oAuth2Client == null ? if (oAuth2Client == null) {
null : toSpringClientRegistration(oAuth2Client); return null;
}
return toSpringClientRegistration(oAuth2Client);
} }
private ClientRegistration toSpringClientRegistration(OAuth2Client oAuth2Client){ private ClientRegistration toSpringClientRegistration(OAuth2Client oAuth2Client) {
String registrationId = oAuth2Client.getUuidId().toString(); String registrationId = oAuth2Client.getUuidId().toString();
// NONE is used if we need pkce-based code challenge // NONE is used if we need pkce-based code challenge
@ -67,4 +70,5 @@ public class HybridClientRegistrationRepository implements ClientRegistrationRep
.redirectUri(defaultRedirectUriTemplate) .redirectUri(defaultRedirectUriTemplate)
.build(); .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> { 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); 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); AdminSettings findByTenantIdAndKey(UUID tenantId, String key);
boolean removeByTenantIdAndKey(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.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.AdminSettings; 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.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.common.data.id.TenantId;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import java.util.Optional;
@Service @Service
@Slf4j @Slf4j
public class AdminSettingsServiceImpl implements AdminSettingsService { public class AdminSettingsServiceImpl implements AdminSettingsService {
@ -87,10 +92,25 @@ public class AdminSettingsServiceImpl implements AdminSettingsService {
} }
@Override @Override
public void deleteAdminSettingsByTenantId(TenantId tenantId) { public void deleteByTenantId(TenantId tenantId) {
adminSettingsDao.removeByTenantId(tenantId.getId()); 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) { private void dropTokenIfProviderInfoChanged(JsonNode newJsonValue, JsonNode oldJsonValue) {
if (newJsonValue.has("enableOauth2") && newJsonValue.get("enableOauth2").asBoolean()) { if (newJsonValue.has("enableOauth2") && newJsonValue.get("enableOauth2").asBoolean()) {
if (!newJsonValue.get("providerId").equals(oldJsonValue.get("providerId")) || 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; import java.util.UUID;
/**
* Created by Valerii Sosliuk on 5/6/2017.
*/
public interface AdminSettingsRepository extends JpaRepository<AdminSettingsEntity, UUID> { public interface AdminSettingsRepository extends JpaRepository<AdminSettingsEntity, UUID> {
AdminSettingsEntity findByTenantIdAndKey(UUID tenantId, String key); 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; package org.thingsboard.server.dao.sql.settings;
import lombok.extern.slf4j.Slf4j; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.AdminSettings; 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.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@ -35,21 +35,10 @@ import java.util.UUID;
@Component @Component
@SqlDao @SqlDao
@Slf4j @RequiredArgsConstructor
public class JpaAdminSettingsDao extends JpaAbstractDao<AdminSettingsEntity, AdminSettings> implements AdminSettingsDao, TenantEntityDao<AdminSettings> { public class JpaAdminSettingsDao extends JpaAbstractDao<AdminSettingsEntity, AdminSettings> implements AdminSettingsDao, TenantEntityDao<AdminSettings> {
@Autowired private final AdminSettingsRepository adminSettingsRepository;
private AdminSettingsRepository adminSettingsRepository;
@Override
protected Class<AdminSettingsEntity> getEntityClass() {
return AdminSettingsEntity.class;
}
@Override
protected JpaRepository<AdminSettingsEntity, UUID> getRepository() {
return adminSettingsRepository;
}
@Override @Override
public AdminSettings findByTenantIdAndKey(UUID tenantId, String key) { 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))); 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.PaginatedRemover;
import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.service.Validator;
import org.thingsboard.server.dao.service.validator.TenantDataValidator; 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.trendz.TrendzSettingsService;
import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService;
import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserService;
@ -76,8 +75,6 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
@Autowired @Autowired
private ApiUsageStateService apiUsageStateService; private ApiUsageStateService apiUsageStateService;
@Autowired @Autowired
private AdminSettingsService adminSettingsService;
@Autowired
private NotificationSettingsService notificationSettingsService; private NotificationSettingsService notificationSettingsService;
@Autowired @Autowired
private QrCodeSettingService qrCodeSettingService; private QrCodeSettingService qrCodeSettingService;
@ -168,7 +165,6 @@ public class TenantServiceImpl extends AbstractCachedEntityService<TenantId, Ten
userService.deleteAllByTenantId(tenantId); userService.deleteAllByTenantId(tenantId);
notificationSettingsService.deleteNotificationSettings(tenantId); notificationSettingsService.deleteNotificationSettings(tenantId);
trendzSettingsService.deleteTrendzSettings(tenantId); trendzSettingsService.deleteTrendzSettings(tenantId);
adminSettingsService.deleteAdminSettingsByTenantId(tenantId);
qrCodeSettingService.deleteByTenantId(tenantId); qrCodeSettingService.deleteByTenantId(tenantId);
tenantDao.removeById(tenantId, tenantId.getId()); 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()); 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 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.ASSET, EntityType.ASSET_PROFILE, EntityType.DEVICE, EntityType.DEVICE_PROFILE,
EntityType.DASHBOARD, EntityType.EDGE, EntityType.RULE_CHAIN, EntityType.API_USAGE_STATE, EntityType.DASHBOARD, EntityType.EDGE, EntityType.RULE_CHAIN, EntityType.API_USAGE_STATE,
EntityType.TB_RESOURCE, EntityType.OTA_PACKAGE, EntityType.RPC, EntityType.QUEUE, 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); 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 @Override
protected PageData<Tenant> findEntities(TenantId tenantId, TenantId id, PageLink pageLink) { 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; package org.thingsboard.mqtt;
/** import java.io.Serial;
* Created by Valerii Sosliuk on 12/26/2017.
*/
public class ChannelClosedException extends RuntimeException { public class ChannelClosedException extends RuntimeException {
@Serial
private static final long serialVersionUID = 6266638352424706909L; private static final long serialVersionUID = 6266638352424706909L;
public ChannelClosedException() { public ChannelClosedException() {
@ -40,4 +40,5 @@ public class ChannelClosedException extends RuntimeException {
public ChannelClosedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { public ChannelClosedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, 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.MqttSubAckMessage;
import io.netty.handler.codec.mqtt.MqttUnsubAckMessage; import io.netty.handler.codec.mqtt.MqttUnsubAckMessage;
/**
* Created by Valerii Sosliuk on 12/30/2017.
*/
public interface MqttClientCallback { public interface MqttClientCallback {
/** /**
@ -53,4 +50,5 @@ public interface MqttClientCallback {
default void onDisconnect(MqttMessage mqttDisconnectMessage) { 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"}) @SuppressWarnings({"WeakerAccess", "unused"})
public final class MqttClientConfig { public final class MqttClientConfig {
@Getter
private final SslContext sslContext; private final SslContext sslContext;
private final String randomClientId; private final String randomClientId;
@Getter @Getter
@Setter @Setter
private String ownerId; // [TenantId][IntegrationId] or [TenantId][RuleNodeId] for exceptions logging purposes private String ownerId; // [TenantId][IntegrationId] or [TenantId][RuleNodeId] for exceptions logging purposes
@Nonnull
@Getter
private String clientId; private String clientId;
@Getter
private int timeoutSeconds = 60; private int timeoutSeconds = 60;
@Getter
private MqttVersion protocolVersion = MqttVersion.MQTT_3_1; private MqttVersion protocolVersion = MqttVersion.MQTT_3_1;
@Nullable private String username = null; @Nullable
@Nullable private String password = null; @Getter
@Setter
private String username = null;
@Nullable
@Getter
@Setter
private String password = null;
@Getter
@Setter
private boolean cleanSession = true; private boolean cleanSession = true;
@Nullable private MqttLastWill lastWill; @Nullable
@Getter
@Setter
private MqttLastWill lastWill;
@Setter
@Getter
private Class<? extends Channel> channelClass = NioSocketChannel.class; private Class<? extends Channel> channelClass = NioSocketChannel.class;
@Getter
@Setter
private boolean reconnect = true; private boolean reconnect = true;
@Getter
private long reconnectDelay = 1L; private long reconnectDelay = 1L;
@Getter
private int maxBytesInMessage = 8092; private int maxBytesInMessage = 8092;
@Getter @Getter
@ -74,109 +97,37 @@ public final class MqttClientConfig {
public MqttClientConfig(SslContext sslContext) { public MqttClientConfig(SslContext sslContext) {
this.sslContext = sslContext; this.sslContext = sslContext;
Random random = new Random(); Random random = new Random();
String id = "netty-mqtt/"; StringBuilder id = new StringBuilder("netty-mqtt/");
String[] options = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split(""); String[] options = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789".split("");
for(int i = 0; i < 8; i++){ for (int i = 0; i < 8; i++) {
id += options[random.nextInt(options.length)]; id.append(options[random.nextInt(options.length)]);
} }
this.clientId = id; this.clientId = id.toString();
this.randomClientId = id; this.randomClientId = id.toString();
}
@Nonnull
public String getClientId() {
return clientId;
} }
public void setClientId(@Nullable String clientId) { public void setClientId(@Nullable String clientId) {
if(clientId == null){ if (clientId == null) {
this.clientId = randomClientId; this.clientId = randomClientId;
}else{ } else {
this.clientId = clientId; this.clientId = clientId;
} }
} }
public int getTimeoutSeconds() {
return timeoutSeconds;
}
public void setTimeoutSeconds(int 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"); throw new IllegalArgumentException("timeoutSeconds must be > 0 or -1");
} }
this.timeoutSeconds = timeoutSeconds; this.timeoutSeconds = timeoutSeconds;
} }
public MqttVersion getProtocolVersion() {
return protocolVersion;
}
public void setProtocolVersion(MqttVersion protocolVersion) { public void setProtocolVersion(MqttVersion protocolVersion) {
if(protocolVersion == null){ if (protocolVersion == null) {
throw new NullPointerException("protocolVersion"); throw new NullPointerException("protocolVersion");
} }
this.protocolVersion = 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. * Sets the reconnect delay in seconds. Defaults to 1 second.
* @param reconnectDelay * @param reconnectDelay
@ -189,10 +140,6 @@ public final class MqttClientConfig {
this.reconnectDelay = reconnectDelay; 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}. * 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. * 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; 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.DefaultPromise;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.common.util.ListeningExecutor;
@ -67,18 +69,28 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
final class MqttClientImpl implements MqttClient { final class MqttClientImpl implements MqttClient {
@Getter(AccessLevel.PACKAGE)
private final Set<String> serverSubscriptions = new HashSet<>(); private final Set<String> serverSubscriptions = new HashSet<>();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<Integer, MqttPendingUnsubscription> pendingServerUnsubscribes = new ConcurrentHashMap<>(); private final ConcurrentMap<Integer, MqttPendingUnsubscription> pendingServerUnsubscribes = new ConcurrentHashMap<>();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<Integer, MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new ConcurrentHashMap<>(); private final ConcurrentMap<Integer, MqttIncomingQos2Publish> qos2PendingIncomingPublishes = new ConcurrentHashMap<>();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<Integer, MqttPendingPublish> pendingPublishes = new ConcurrentHashMap<>(); private final ConcurrentMap<Integer, MqttPendingPublish> pendingPublishes = new ConcurrentHashMap<>();
@Getter(AccessLevel.PACKAGE)
private final HashMultimap<String, MqttSubscription> subscriptions = HashMultimap.create(); private final HashMultimap<String, MqttSubscription> subscriptions = HashMultimap.create();
@Getter(AccessLevel.PACKAGE)
private final ConcurrentMap<Integer, MqttPendingSubscription> pendingSubscriptions = new ConcurrentHashMap<>(); private final ConcurrentMap<Integer, MqttPendingSubscription> pendingSubscriptions = new ConcurrentHashMap<>();
@Getter(AccessLevel.PACKAGE)
private final Set<String> pendingSubscribeTopics = new HashSet<>(); private final Set<String> pendingSubscribeTopics = new HashSet<>();
@Getter(AccessLevel.PACKAGE)
private final HashMultimap<MqttHandler, MqttSubscription> handlerToSubscription = HashMultimap.create(); private final HashMultimap<MqttHandler, MqttSubscription> handlerToSubscription = HashMultimap.create();
private final AtomicInteger nextMessageId = new AtomicInteger(1); private final AtomicInteger nextMessageId = new AtomicInteger(1);
@Getter
private final MqttClientConfig clientConfig; private final MqttClientConfig clientConfig;
@Getter(AccessLevel.PACKAGE)
private final MqttHandler defaultHandler; private final MqttHandler defaultHandler;
private final ReconnectStrategy reconnectStrategy; private final ReconnectStrategy reconnectStrategy;
@ -88,12 +100,15 @@ final class MqttClientImpl implements MqttClient {
private volatile Channel channel; private volatile Channel channel;
private volatile boolean disconnected = false; private volatile boolean disconnected = false;
@Getter
private volatile boolean reconnect = false; private volatile boolean reconnect = false;
private String host; private String host;
private int port; private int port;
@Getter @Getter
@Setter
private MqttClientCallback callback; private MqttClientCallback callback;
@Getter
private final ListeningExecutor handlerExecutor; private final ListeningExecutor handlerExecutor;
private final static int DISCONNECT_FALLBACK_DELAY_SECS = 1; private final static int DISCONNECT_FALLBACK_DELAY_SECS = 1;
@ -240,11 +255,6 @@ final class MqttClientImpl implements MqttClient {
this.eventLoop = eventLoop; 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 * 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; return future;
} }
/**
* Retrieve the MqttClient configuration
*
* @return The {@link MqttClientConfig} instance we use
*/
@Override
public MqttClientConfig getClientConfig() {
return clientConfig;
}
@Override @Override
public void disconnect() { public void disconnect() {
if (disconnected) { if (disconnected) {
@ -480,25 +480,15 @@ final class MqttClientImpl implements MqttClient {
} }
} }
@Override
public void setCallback(MqttClientCallback callback) {
this.callback = callback;
}
///////////////////////////////////////////// PRIVATE API ///////////////////////////////////////////// ///////////////////////////////////////////// PRIVATE API /////////////////////////////////////////////
public boolean isReconnect() {
return reconnect;
}
public void onSuccessfulReconnect() { public void onSuccessfulReconnect() {
if (callback != null) { if (callback != null) {
callback.onSuccessfulReconnect(); callback.onSuccessfulReconnect();
} }
} }
ChannelFuture sendAndFlushPacket(Object message) { ChannelFuture sendAndFlushPacket(Object message) {
if (this.channel == null) { if (this.channel == null) {
return null; return null;
@ -576,7 +566,7 @@ final class MqttClientImpl implements MqttClient {
} }
private void checkSubscriptions(String topic, Promise<Void> promise) { 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); MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBSCRIBE, false, MqttQoS.AT_LEAST_ONCE, false, 0);
MqttMessageIdVariableHeader variableHeader = getNewMessageId(); MqttMessageIdVariableHeader variableHeader = getNewMessageId();
MqttUnsubscribePayload payload = new MqttUnsubscribePayload(Collections.singletonList(topic)); 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 class MqttChannelInitializer extends ChannelInitializer<SocketChannel> {
private final Promise<MqttConnectResult> connectFuture; 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("mqttPingHandler", new MqttPingHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds()));
ch.pipeline().addLast("mqttHandler", new MqttChannelHandler(MqttClientImpl.this, connectFuture)); 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.channel.ChannelFuture;
import io.netty.handler.codec.mqtt.MqttConnectReturnCode; import io.netty.handler.codec.mqtt.MqttConnectReturnCode;
import lombok.Getter;
import lombok.ToString; import lombok.ToString;
@ToString @ToString
@SuppressWarnings({"WeakerAccess", "unused"}) @SuppressWarnings({"WeakerAccess", "unused"})
public final class MqttConnectResult { public final class MqttConnectResult {
@Getter
private final boolean success; private final boolean success;
@Getter
private final MqttConnectReturnCode returnCode; private final MqttConnectReturnCode returnCode;
@Getter
private final ChannelFuture closeFuture; private final ChannelFuture closeFuture;
MqttConnectResult(boolean success, MqttConnectReturnCode returnCode, ChannelFuture closeFuture) { MqttConnectResult(boolean success, MqttConnectReturnCode returnCode, ChannelFuture closeFuture) {
@ -33,16 +37,4 @@ public final class MqttConnectResult {
this.closeFuture = closeFuture; 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; package org.thingsboard.mqtt;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.Setter;
import java.util.regex.Pattern; import java.util.regex.Pattern;
final class MqttSubscription { final class MqttSubscription {
@Getter(AccessLevel.PACKAGE)
private final String topic; private final String topic;
private final Pattern topicRegex; private final Pattern topicRegex;
@Getter
private final MqttHandler handler; private final MqttHandler handler;
@Getter(AccessLevel.PACKAGE)
private final boolean once; private final boolean once;
@Getter(AccessLevel.PACKAGE)
@Setter(AccessLevel.PACKAGE)
private volatile boolean called; private volatile boolean called;
MqttSubscription(String topic, MqttHandler handler, boolean once) { MqttSubscription(String topic, MqttHandler handler, boolean once) {
@ -40,22 +47,6 @@ final class MqttSubscription {
this.topicRegex = Pattern.compile(topic.replace("+", "[^/]+").replace("#", ".+") + "$"); 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) { boolean matches(String topic) {
return this.topicRegex.matcher(topic).matches(); return this.topicRegex.matcher(topic).matches();
} }
@ -78,7 +69,4 @@ final class MqttSubscription {
return result; return result;
} }
void setCalled(boolean called) {
this.called = called;
}
} }

2
pom.xml

@ -86,7 +86,7 @@
<zookeeper.version>3.9.3</zookeeper.version> <zookeeper.version>3.9.3</zookeeper.version>
<protobuf.version>3.25.5</protobuf.version> <!-- A Major v4 does not support by the pubsub yet--> <protobuf.version>3.25.5</protobuf.version> <!-- A Major v4 does not support by the pubsub yet-->
<grpc.version>1.63.0</grpc.version> <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> <lombok.version>1.18.32</lombok.version>
<paho.client.version>1.2.5</paho.client.version> <paho.client.version>1.2.5</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.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 org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@ -64,12 +63,10 @@ import java.util.concurrent.TimeoutException;
) )
public class TbMqttNode extends TbAbstractExternalNode { public class TbMqttNode extends TbAbstractExternalNode {
private static final Charset UTF8 = StandardCharsets.UTF_8; private static final int MQTT_3_MAX_CLIENT_ID_LENGTH = 23;
private static final int MQTT_5_MAX_CLIENT_ID_LENGTH = 256;
private static final String ERROR = "error";
protected TbMqttNodeConfiguration mqttNodeConfiguration; protected TbMqttNodeConfiguration mqttNodeConfiguration;
protected MqttClient mqttClient; protected MqttClient mqttClient;
@Override @Override
@ -87,9 +84,9 @@ public class TbMqttNode extends TbAbstractExternalNode {
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { 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); 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()) MqttQoS.AT_LEAST_ONCE, mqttNodeConfiguration.isRetainedMessage())
.addListener(future -> { .addListener(future -> {
if (future.isSuccess()) { if (future.isSuccess()) {
@ -103,7 +100,7 @@ public class TbMqttNode extends TbAbstractExternalNode {
private TbMsg processException(TbMsg origMsg, Throwable e) { private TbMsg processException(TbMsg origMsg, Throwable e) {
TbMsgMetaData metaData = origMsg.getMetaData().copy(); TbMsgMetaData metaData = origMsg.getMetaData().copy();
metaData.putValue(ERROR, e.getClass() + ": " + e.getMessage()); metaData.putValue("error", e.getClass() + ": " + e.getMessage());
return origMsg.transform() return origMsg.transform()
.metaData(metaData) .metaData(metaData)
.build(); .build();
@ -111,8 +108,8 @@ public class TbMqttNode extends TbAbstractExternalNode {
@Override @Override
public void destroy() { public void destroy() {
if (this.mqttClient != null) { if (mqttClient != null) {
this.mqttClient.disconnect(); mqttClient.disconnect();
} }
} }
@ -123,11 +120,11 @@ public class TbMqttNode extends TbAbstractExternalNode {
protected MqttClient initClient(TbContext ctx) throws Exception { protected MqttClient initClient(TbContext ctx) throws Exception {
MqttClientConfig config = new MqttClientConfig(getSslContext()); MqttClientConfig config = new MqttClientConfig(getSslContext());
config.setOwnerId(getOwnerId(ctx)); config.setOwnerId(getOwnerId(ctx));
if (!StringUtils.isEmpty(this.mqttNodeConfiguration.getClientId())) { if (!StringUtils.isEmpty(mqttNodeConfiguration.getClientId())) {
config.setClientId(getClientId(ctx)); config.setClientId(getClientId(ctx));
} }
config.setCleanSession(this.mqttNodeConfiguration.isCleanSession()); config.setCleanSession(mqttNodeConfiguration.isCleanSession());
config.setProtocolVersion(this.mqttNodeConfiguration.getProtocolVersion()); config.setProtocolVersion(mqttNodeConfiguration.getProtocolVersion());
MqttClientSettings mqttClientSettings = ctx.getMqttClientSettings(); MqttClientSettings mqttClientSettings = ctx.getMqttClientSettings();
config.setRetransmissionConfig(new MqttClientConfig.RetransmissionConfig( config.setRetransmissionConfig(new MqttClientConfig.RetransmissionConfig(
@ -139,32 +136,32 @@ public class TbMqttNode extends TbAbstractExternalNode {
prepareMqttClientConfig(config); prepareMqttClientConfig(config);
MqttClient client = getMqttClient(ctx, config); MqttClient client = getMqttClient(ctx, config);
client.setEventLoop(ctx.getSharedEventLoop()); 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; MqttConnectResult result;
try { try {
result = connectFuture.get(this.mqttNodeConfiguration.getConnectTimeoutSec(), TimeUnit.SECONDS); result = connectFuture.get(mqttNodeConfiguration.getConnectTimeoutSec(), TimeUnit.SECONDS);
} catch (TimeoutException ex) { } catch (TimeoutException ex) {
connectFuture.cancel(true); connectFuture.cancel(true);
client.disconnect(); 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)); throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s.", hostPort));
} }
if (!result.isSuccess()) { if (!result.isSuccess()) {
connectFuture.cancel(true); connectFuture.cancel(true);
client.disconnect(); 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())); throw new RuntimeException(String.format("Failed to connect to MQTT broker at %s. Result code is: %s", hostPort, result.getReturnCode()));
} }
return client; return client;
} }
private String getClientId(TbContext ctx) throws TbNodeException { private String getClientId(TbContext ctx) throws TbNodeException {
String clientId = this.mqttNodeConfiguration.isAppendClientIdSuffix() ? String clientId = mqttNodeConfiguration.isAppendClientIdSuffix() ?
this.mqttNodeConfiguration.getClientId() + "_" + ctx.getServiceId() : mqttNodeConfiguration.getClientId() + "_" + ctx.getServiceId() :
this.mqttNodeConfiguration.getClientId(); mqttNodeConfiguration.getClientId();
if (clientId.length() > 23) { int maxLength = mqttNodeConfiguration.getProtocolVersion() == MqttVersion.MQTT_3_1 ? MQTT_3_MAX_CLIENT_ID_LENGTH : MQTT_5_MAX_CLIENT_ID_LENGTH;
throw new TbNodeException("Client ID is too long '" + clientId + "'. " + if (clientId.length() > maxLength) {
"The length of Client ID cannot be longer than 23, but current length is " + clientId.length() + ".", true); throw new TbNodeException("The length of Client ID cannot be longer than " + maxLength + ", but current length is " + clientId.length() + ".", true);
} }
return clientId; return clientId;
} }
@ -174,7 +171,7 @@ public class TbMqttNode extends TbAbstractExternalNode {
} }
protected void prepareMqttClientConfig(MqttClientConfig config) { protected void prepareMqttClientConfig(MqttClientConfig config) {
ClientCredentials credentials = this.mqttNodeConfiguration.getCredentials(); ClientCredentials credentials = mqttNodeConfiguration.getCredentials();
if (credentials.getType() == CredentialsType.BASIC) { if (credentials.getType() == CredentialsType.BASIC) {
BasicCredentials basicCredentials = (BasicCredentials) credentials; BasicCredentials basicCredentials = (BasicCredentials) credentials;
config.setUsername(basicCredentials.getUsername()); config.setUsername(basicCredentials.getUsername());
@ -183,7 +180,7 @@ public class TbMqttNode extends TbAbstractExternalNode {
} }
private SslContext getSslContext() throws SSLException { 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) { 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)); tenantEntity = ctx.getNotificationRequestService().findNotificationRequestById(ctxTenantId, new NotificationRequestId(id));
break; break;
case NOTIFICATION: case NOTIFICATION:
case ADMIN_SETTINGS:
return ctxTenantId; return ctxTenantId;
case NOTIFICATION_RULE: case NOTIFICATION_RULE:
tenantEntity = ctx.getNotificationRuleService().findNotificationRuleById(ctxTenantId, new NotificationRuleId(id)); 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)))); assertThatNoException().isThrownBy(() -> mqttNode.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(mqttNodeConfig))));
} }
@Test @ParameterizedTest
public void givenClientIdIsTooLong_whenInit_thenThrowsException() { @MethodSource("provideInvalidClientIdScenarios")
String invalidClientId = "vhfrbeb38ygwfwrgfwefgterhytjytj"; public void givenInvalidClientId_whenInit_thenThrowsException(MqttVersion version, int maxLength, int repeat, String serviceId, boolean appendSuffix) {
mqttNodeConfig.setClientId(invalidClientId); String baseClientId = "x".repeat(repeat);
mqttNodeConfig.setClientId(baseClientId);
mqttNodeConfig.setAppendClientIdSuffix(appendSuffix);
mqttNodeConfig.setProtocolVersion(version);
given(ctxMock.getTenantId()).willReturn(TENANT_ID); given(ctxMock.getTenantId()).willReturn(TENANT_ID);
given(ctxMock.getSelf()).willReturn(new RuleNode(RULE_NODE_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)))) assertThatThrownBy(() -> mqttNode.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(mqttNodeConfig))))
.isInstanceOf(TbNodeException.class) .isInstanceOf(TbNodeException.class)
.hasMessage("Client ID is too long '" + invalidClientId + "'. " + .hasMessage(expectedMessage)
"The length of Client ID cannot be longer than 23, but current length is " + invalidClientId.length() + ".")
.extracting(e -> ((TbNodeException) e).isUnrecoverable()) .extracting(e -> ((TbNodeException) e).isUnrecoverable())
.isEqualTo(true); .isEqualTo(true);
} }
@Test private static Stream<Arguments> provideInvalidClientIdScenarios() {
public void givenClientIdIsOkAndAppendClientIdSuffixIsTrue_whenInit_thenClientIdBecomesInvalidAndThrowsException() { return Stream.of(
String validClientId = "fertjnhnjj4ge"; // MQTT_5, too long clientId
mqttNodeConfig.setClientId("fertjnhnjj4ge"); Arguments.of(MqttVersion.MQTT_5, 256, 257, null, false),
mqttNodeConfig.setAppendClientIdSuffix(true);
given(ctxMock.getTenantId()).willReturn(TENANT_ID); // MQTT_5, base + suffix exceeds
given(ctxMock.getSelf()).willReturn(new RuleNode(RULE_NODE_ID)); Arguments.of(MqttVersion.MQTT_5, 256, 250, "test-service", true),
String serviceId = "test-service";
given(ctxMock.getServiceId()).willReturn(serviceId);
String resultedClientId = validClientId + "_" + serviceId; // MQTT_3_1, too long clientId
assertThatThrownBy(() -> mqttNode.init(ctxMock, new TbNodeConfiguration(JacksonUtil.valueToTree(mqttNodeConfig)))) Arguments.of(MqttVersion.MQTT_3_1, 23, 24, null, false),
.isInstanceOf(TbNodeException.class)
.hasMessage("Client ID is too long '" + resultedClientId + "'. " + // MQTT_3_1, base + suffix exceeds
"The length of Client ID cannot be longer than 23, but current length is " + resultedClientId.length() + ".") Arguments.of(MqttVersion.MQTT_3_1, 23, 5, "verylongservicename", true)
.extracting(e -> ((TbNodeException) e).isUnrecoverable()) );
.isEqualTo(true);
} }
@Test @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.RuleEngineDeviceProfileCache;
import org.thingsboard.rule.engine.api.RuleEngineRpcService; import org.thingsboard.rule.engine.api.RuleEngineRpcService;
import org.thingsboard.rule.engine.api.TbContext; 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.ApiUsageState;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.Dashboard;
@ -167,7 +168,6 @@ public class TenantIdLoaderTest {
private TenantId tenantId; private TenantId tenantId;
private TenantProfileId tenantProfileId; private TenantProfileId tenantProfileId;
private NotificationId notificationId;
private AbstractListeningExecutor dbExecutor; private AbstractListeningExecutor dbExecutor;
@BeforeEach @BeforeEach
@ -179,9 +179,8 @@ public class TenantIdLoaderTest {
} }
}; };
dbExecutor.init(); dbExecutor.init();
this.tenantId = new TenantId(UUID.randomUUID()); this.tenantId = TenantId.fromUUID(UUID.randomUUID());
this.tenantProfileId = new TenantProfileId(UUID.randomUUID()); this.tenantProfileId = new TenantProfileId(UUID.randomUUID());
this.notificationId = new NotificationId(UUID.randomUUID());
when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getTenantId()).thenReturn(tenantId);
@ -199,6 +198,7 @@ public class TenantIdLoaderTest {
switch (entityType) { switch (entityType) {
case TENANT: case TENANT:
case NOTIFICATION: case NOTIFICATION:
case ADMIN_SETTINGS:
break; break;
case CUSTOMER: case CUSTOMER:
Customer customer = new Customer(); Customer customer = new Customer();
@ -465,7 +465,7 @@ public class TenantIdLoaderTest {
@Test @Test
public void test_findEntityIdAsync_other_tenant() { public void test_findEntityIdAsync_other_tenant() {
checkTenant(new TenantId(UUID.randomUUID()), false); checkTenant(TenantId.fromUUID(UUID.randomUUID()), false);
} }
} }

Loading…
Cancel
Save