Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard

pull/5552/head
Igor Kulikov 5 years ago
parent
commit
3860275f67
  1. 1
      application/pom.xml
  2. 1
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  3. 10
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  4. 45
      application/src/main/java/org/thingsboard/server/controller/AdminController.java
  5. 128
      application/src/main/java/org/thingsboard/server/controller/AssetController.java
  6. 86
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  7. 42
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  8. 34
      application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
  9. 60
      application/src/main/java/org/thingsboard/server/controller/CustomerController.java
  10. 202
      application/src/main/java/org/thingsboard/server/controller/DashboardController.java
  11. 187
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  12. 3
      application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
  13. 4
      application/src/main/java/org/thingsboard/server/controller/TenantController.java
  14. 7
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
  15. 4
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
  16. 30
      application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java
  17. 31
      application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java
  18. 32
      application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java
  19. 29
      application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java
  20. 30
      application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java
  21. 7
      application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java
  22. 4
      common/data/pom.xml
  23. 18
      common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java
  24. 5
      common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
  25. 25
      common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
  26. 34
      common/data/src/main/java/org/thingsboard/server/common/data/Device.java
  27. 6
      common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java
  28. 6
      common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java
  29. 5
      common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java
  30. 6
      common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java
  31. 5
      common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java
  32. 33
      common/data/src/main/java/org/thingsboard/server/common/data/User.java
  33. 30
      common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
  34. 5
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
  35. 4
      common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java
  36. 6
      common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java
  37. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java
  38. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java
  39. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java
  40. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java
  41. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java
  42. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java
  43. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java
  44. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java
  45. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java
  46. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java
  47. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java
  48. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java
  49. 2
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java
  50. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java
  51. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java
  52. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java
  53. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java
  54. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java
  55. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java
  56. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java
  57. 5
      common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java
  58. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java
  59. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java
  60. 3
      common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java
  61. 7
      common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java
  62. 26
      common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java
  63. 9
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java
  64. 28
      common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java
  65. 7
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java
  66. 10
      common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java
  67. 6
      common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java
  68. 6
      common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java
  69. 6
      common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java
  70. 28
      common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java
  71. 5
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  72. 22
      dao/pom.xml
  73. 39
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
  74. 30
      dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java
  75. 64
      dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java
  76. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
  77. 200
      dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
  78. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java
  79. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java
  80. 2
      dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java
  81. 33
      dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java
  82. 8
      dao/src/test/java/org/thingsboard/server/dao/service/psql/EntityServicePostgreSqlTest.java
  83. 57
      dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepositoryTest.java
  84. 47
      dao/src/test/resources/psql-test.properties
  85. 2
      dao/src/test/resources/sql/system-test-psql.sql
  86. 17
      pom.xml
  87. 16
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java
  88. 30
      rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java
  89. 10
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNodeConfiguration.java
  90. 22
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
  91. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
  92. 12
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
  93. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
  94. 68
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java
  95. 61
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java

1
application/pom.xml

@ -360,6 +360,7 @@
</systemPropertyVariables> </systemPropertyVariables>
<excludes> <excludes>
<exclude>**/sql/*Test.java</exclude> <exclude>**/sql/*Test.java</exclude>
<exclude>**/psql/*Test.java</exclude>
<exclude>**/nosql/*Test.java</exclude> <exclude>**/nosql/*Test.java</exclude>
</excludes> </excludes>
<includes> <includes>

1
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -731,6 +731,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
} }
private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) { private void notifyTransportAboutClosedSessionMaxSessionsLimit(UUID sessionId, SessionInfoMetaData sessionMd) {
log.debug("remove eldest session (max concurrent sessions limit reached per device) sessionId [{}] sessionMd [{}]", sessionId, sessionMd);
notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!"); notifyTransportAboutClosedSession(sessionId, sessionMd, "max concurrent sessions limit reached per device!");
} }

10
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -190,13 +190,13 @@ class DefaultTbContext implements TbContext {
@Override @Override
public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) { public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName); TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
enqueueForTellNext(tpi, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure); enqueueForTellNext(tpi, queueName, tbMsg, Collections.singleton(relationType), null, onSuccess, onFailure);
} }
@Override @Override
public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) { public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set<String> relationTypes, Runnable onSuccess, Consumer<Throwable> onFailure) {
TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName); TopicPartitionInfo tpi = resolvePartition(tbMsg, queueName);
enqueueForTellNext(tpi, tbMsg, relationTypes, null, onSuccess, onFailure); enqueueForTellNext(tpi, queueName, tbMsg, relationTypes, null, onSuccess, onFailure);
} }
private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) { private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) {
@ -211,9 +211,13 @@ class DefaultTbContext implements TbContext {
} }
private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) { private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) {
enqueueForTellNext(tpi, source.getQueueName(), source, relationTypes, failureMessage, onSuccess, onFailure);
}
private void enqueueForTellNext(TopicPartitionInfo tpi, String queueName, TbMsg source, Set<String> relationTypes, String failureMessage, Runnable onSuccess, Consumer<Throwable> onFailure) {
RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId(); RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId();
RuleNodeId ruleNodeId = nodeCtx.getSelf().getId(); RuleNodeId ruleNodeId = nodeCtx.getSelf().getId();
TbMsg tbMsg = TbMsg.newMsg(source, ruleChainId, ruleNodeId); TbMsg tbMsg = TbMsg.newMsg(source, queueName, ruleChainId, ruleNodeId);
TransportProtos.ToRuleEngineMsg.Builder msg = TransportProtos.ToRuleEngineMsg.newBuilder() TransportProtos.ToRuleEngineMsg.Builder msg = TransportProtos.ToRuleEngineMsg.newBuilder()
.setTenantIdMSB(getTenantId().getId().getMostSignificantBits()) .setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(getTenantId().getId().getLeastSignificantBits()) .setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())

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

@ -16,6 +16,8 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -44,6 +46,7 @@ import org.thingsboard.server.service.update.UpdateService;
@RequestMapping("/api/admin") @RequestMapping("/api/admin")
public class AdminController extends BaseController { public class AdminController extends BaseController {
public static final String SYS_ADMIN_AUTHORITY_ONLY = " Available for users with System Administrator ('SYS_ADMIN') authority only.";
@Autowired @Autowired
private MailService mailService; private MailService mailService;
@ -59,10 +62,14 @@ public class AdminController extends BaseController {
@Autowired @Autowired
private UpdateService updateService; private UpdateService updateService;
@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." + SYS_ADMIN_AUTHORITY_ONLY)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/{key}", method = RequestMethod.GET) @RequestMapping(value = "/settings/{key}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public AdminSettings getAdminSettings(@PathVariable("key") String key) throws ThingsboardException { public AdminSettings getAdminSettings(
@ApiParam(value = "A string value of the key (e.g. 'general' or 'mail').")
@PathVariable("key") String key) throws ThingsboardException {
try { try {
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)); AdminSettings adminSettings = checkNotNull(adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, key));
@ -75,10 +82,17 @@ public class AdminController extends BaseController {
} }
} }
@ApiOperation(value = "Get the Administration Settings object using key (getAdminSettings)",
notes = "Creates or Updates the Administration Settings. Platform generates random Administration Settings Id during settings creation. " +
"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." + SYS_ADMIN_AUTHORITY_ONLY)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings", method = RequestMethod.POST) @RequestMapping(value = "/settings", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public AdminSettings saveAdminSettings(@RequestBody AdminSettings adminSettings) throws ThingsboardException { public AdminSettings saveAdminSettings(
@ApiParam(value = "A JSON value representing the Administration Settings.")
@RequestBody AdminSettings adminSettings) throws ThingsboardException {
try { try {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE);
adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings)); adminSettings = checkNotNull(adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminSettings));
@ -94,6 +108,8 @@ public class AdminController extends BaseController {
} }
} }
@ApiOperation(value = "Get the Security Settings object",
notes = "Get the Security Settings object that contains password policy, etc." + SYS_ADMIN_AUTHORITY_ONLY)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/securitySettings", method = RequestMethod.GET) @RequestMapping(value = "/securitySettings", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -106,10 +122,14 @@ public class AdminController extends BaseController {
} }
} }
@ApiOperation(value = "Update Security Settings (saveSecuritySettings)",
notes = "Updates the Security Settings object that contains password policy, etc." + SYS_ADMIN_AUTHORITY_ONLY)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/securitySettings", method = RequestMethod.POST) @RequestMapping(value = "/securitySettings", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public SecuritySettings saveSecuritySettings(@RequestBody SecuritySettings securitySettings) throws ThingsboardException { public SecuritySettings saveSecuritySettings(
@ApiParam(value = "A JSON value representing the Security Settings.")
@RequestBody SecuritySettings securitySettings) throws ThingsboardException {
try { try {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE);
securitySettings = checkNotNull(systemSecurityService.saveSecuritySettings(TenantId.SYS_TENANT_ID, securitySettings)); securitySettings = checkNotNull(systemSecurityService.saveSecuritySettings(TenantId.SYS_TENANT_ID, securitySettings));
@ -119,14 +139,19 @@ public class AdminController extends BaseController {
} }
} }
@ApiOperation(value = "Send test email (sendTestMail)",
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. " + SYS_ADMIN_AUTHORITY_ONLY)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/testMail", method = RequestMethod.POST) @RequestMapping(value = "/settings/testMail", method = RequestMethod.POST)
public void sendTestMail(@RequestBody AdminSettings adminSettings) throws ThingsboardException { public void sendTestMail(
@ApiParam(value = "A JSON value representing the Mail Settings.")
@RequestBody AdminSettings adminSettings) throws ThingsboardException {
try { try {
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")) {
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"));
((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText()); ((ObjectNode) adminSettings.getJsonValue()).put("password", mailSettings.getJsonValue().get("password").asText());
} }
@ -138,9 +163,14 @@ public class AdminController extends BaseController {
} }
} }
@ApiOperation(value = "Send test sms (sendTestMail)",
notes = "Attempts to send test sms to the System Administrator User using SMS Settings and phone number provided as a parameters of the request. "
+ SYS_ADMIN_AUTHORITY_ONLY)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/settings/testSms", method = RequestMethod.POST) @RequestMapping(value = "/settings/testSms", method = RequestMethod.POST)
public void sendTestSms(@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException { public void sendTestSms(
@ApiParam(value = "A JSON value representing the Test SMS request.")
@RequestBody TestSmsRequest testSmsRequest) throws ThingsboardException {
try { try {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
smsService.sendTestSms(testSmsRequest); smsService.sendTestSms(testSmsRequest);
@ -149,6 +179,9 @@ public class AdminController extends BaseController {
} }
} }
@ApiOperation(value = "Check for new Platform Releases (checkUpdates)",
notes = "Check notifications about new platform releases. "
+ SYS_ADMIN_AUTHORITY_ONLY)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/updates", method = RequestMethod.GET) @RequestMapping(value = "/updates", method = RequestMethod.GET)
@ResponseBody @ResponseBody

128
application/src/main/java/org/thingsboard/server/controller/AssetController.java

@ -16,9 +16,12 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
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;
@ -37,8 +40,8 @@ import org.thingsboard.server.common.data.asset.AssetInfo;
import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.asset.AssetSearchQuery;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
@ -61,9 +64,8 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
import static org.thingsboard.server.controller.EdgeController.EDGE_ID; import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@RestController @RestController
@TbCoreComponent @TbCoreComponent
@ -75,10 +77,15 @@ public class AssetController extends BaseController {
public static final String ASSET_ID = "assetId"; public static final String ASSET_ID = "assetId";
@ApiOperation(value = "Get Asset (getAssetById)",
notes = "Fetch the Asset object based on the provided Asset Id. " +
"If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET) @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Asset getAssetById(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { public Asset getAssetById(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION)
@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
AssetId assetId = new AssetId(toUUID(strAssetId)); AssetId assetId = new AssetId(toUUID(strAssetId));
@ -88,10 +95,15 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get Asset Info (getAssetInfoById)",
notes = "Fetch the Asset Info object based on the provided Asset Id. " +
"If the user has the authority of 'Tenant Administrator', the server checks that the asset is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the asset is assigned to the same customer. " + ASSET_INFO_DESCRIPTION, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET) @RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public AssetInfo getAssetInfoById(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { public AssetInfo getAssetInfoById(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION)
@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
AssetId assetId = new AssetId(toUUID(strAssetId)); AssetId assetId = new AssetId(toUUID(strAssetId));
@ -101,10 +113,15 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Create Or Update Asset (saveAsset)",
notes = "Creates or Updates the Asset. When creating asset, platform generates Asset Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address) " +
"The newly created Asset id will be present in the response. " +
"Specify existing Asset id to update the asset. " +
"Referencing non-existing Asset Id will cause 'Not Found' error.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset", method = RequestMethod.POST) @RequestMapping(value = "/asset", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Asset saveAsset(@RequestBody Asset asset) throws ThingsboardException { public Asset saveAsset(@ApiParam(value = "A JSON value representing the asset.") @RequestBody Asset asset) throws ThingsboardException {
try { try {
if (TB_SERVICE_QUEUE.equals(asset.getType())) { if (TB_SERVICE_QUEUE.equals(asset.getType())) {
throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS);
@ -140,10 +157,12 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Delete asset (deleteAsset)",
notes = "Deletes the asset and all the relations (from and to the asset). Referencing non-existing asset Id will cause an error.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE) @RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void deleteAsset(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { public void deleteAsset(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
AssetId assetId = new AssetId(toUUID(strAssetId)); AssetId assetId = new AssetId(toUUID(strAssetId));
@ -167,11 +186,13 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Assign asset to customer (assignAssetToCustomer)",
notes = "Creates assignment of the asset to customer. Customer will be able to query asset afterwards.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Asset assignAssetToCustomer(@PathVariable("customerId") String strCustomerId, public Asset assignAssetToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId,
@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
@ -201,10 +222,12 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Unassign asset from customer (unassignAssetFromCustomer)",
notes = "Clears assignment of the asset to customer. Customer will not be able to query asset afterwards.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Asset unassignAssetFromCustomer(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { public Asset unassignAssetFromCustomer(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
AssetId assetId = new AssetId(toUUID(strAssetId)); AssetId assetId = new AssetId(toUUID(strAssetId));
@ -235,10 +258,14 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Make asset publicly available (assignAssetToPublicCustomer)",
notes = "Asset will be available for non-authorized (not logged-in) users. " +
"This is useful to create dashboards that you plan to share/embed on a publicly available website. " +
"However, users that are logged-in and belong to different tenant will not be able to access the asset.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Asset assignAssetToPublicCustomer(@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { public Asset assignAssetToPublicCustomer(@ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
AssetId assetId = new AssetId(toUUID(strAssetId)); AssetId assetId = new AssetId(toUUID(strAssetId));
@ -261,15 +288,24 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Assets (getTenantAssets)",
notes = "Returns a page of assets owned by tenant. " +
PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Asset> getTenantAssets( public PageData<Asset> getTenantAssets(
@ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
@ -284,15 +320,24 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Asset Infos (getTenantAssetInfos)",
notes = "Returns a page of assets info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<AssetInfo> getTenantAssetInfos( public PageData<AssetInfo> getTenantAssetInfos(
@ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
@ -307,10 +352,14 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Asset (getTenantAsset)",
notes = "Requested asset must be owned by tenant that the user belongs to. " +
"Asset name is an unique property of asset. So it can be used to identify the asset.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Asset getTenantAsset( public Asset getTenantAsset(
@ApiParam(value = ASSET_NAME_DESCRIPTION)
@RequestParam String assetName) throws ThingsboardException { @RequestParam String assetName) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
@ -320,16 +369,26 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get Customer Assets (getCustomerAssets)",
notes = "Returns a page of assets objects assigned to customer. " +
PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Asset> getCustomerAssets( public PageData<Asset> getCustomerAssets(
@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
try { try {
@ -347,16 +406,26 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get Customer Asset Infos (getCustomerAssetInfos)",
notes = "Returns a page of assets info objects assigned to customer. " +
PAGE_DATA_PARAMETERS + ASSET_INFO_DESCRIPTION, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<AssetInfo> getCustomerAssetInfos( public PageData<AssetInfo> getCustomerAssetInfos(
@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
try { try {
@ -374,10 +443,13 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get Assets By Ids (getAssetsByIds)",
notes = "Requested assets must be owned by tenant or assigned to customer which user is performing the request. ", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET) @RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<Asset> getAssetsByIds( public List<Asset> getAssetsByIds(
@ApiParam(value = "A list of assets ids, separated by comma ','")
@RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException { @RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException {
checkArrayParameter("assetIds", strAssetIds); checkArrayParameter("assetIds", strAssetIds);
try { try {
@ -400,6 +472,10 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Find related assets (findByQuery)",
notes = "Returns all assets that are related to the specific entity. " +
"The entity id, relation type, asset types, depth of the search, and other query parameters defined using complex 'AssetSearchQuery' object. " +
"See 'Model' tab of the Parameters for more info.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/assets", method = RequestMethod.POST) @RequestMapping(value = "/assets", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -424,6 +500,8 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get Asset Types (getAssetTypes)",
notes = "Returns a set of unique asset types based on assets that are either owned by the tenant or assigned to the customer which user is performing the request.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/asset/types", method = RequestMethod.GET) @RequestMapping(value = "/asset/types", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -438,11 +516,15 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Assign asset to edge (assignAssetToEdge)",
notes = "Creates assignment of an existing asset to an instance of The Edge. " +
"The Edge is a software product for edge computing. " +
"It allows bringing data analysis and management to the edge, while seamlessly synchronizing with the platform server (cloud). ", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Asset assignAssetToEdge(@PathVariable(EDGE_ID) String strEdgeId, public Asset assignAssetToEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId,
@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
@ -471,11 +553,13 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Unassign asset from edge (unassignAssetFromEdge)",
notes = "Clears assignment of the asset to the edge", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Asset unassignAssetFromEdge(@PathVariable(EDGE_ID) String strEdgeId, public Asset unassignAssetFromEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId,
@PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException { @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
checkParameter(ASSET_ID, strAssetId); checkParameter(ASSET_ID, strAssetId);
try { try {
@ -504,18 +588,30 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Get assets assigned to edge (getEdgeAssets)",
notes = "Returns a page of assets assigned to edge. " +
PAGE_DATA_PARAMETERS, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Asset> getEdgeAssets( public PageData<Asset> getEdgeAssets(
@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION)
@PathVariable(EDGE_ID) String strEdgeId, @PathVariable(EDGE_ID) String strEdgeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder, @RequestParam(required = false) String sortOrder,
@ApiParam(value = "Timestamp. Assets with creation time before it won't be queried")
@RequestParam(required = false) Long startTime, @RequestParam(required = false) Long startTime,
@ApiParam(value = "Timestamp. Assets with creation time after it won't be queried")
@RequestParam(required = false) Long endTime) throws ThingsboardException { @RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
try { try {
@ -547,6 +643,8 @@ public class AssetController extends BaseController {
} }
} }
@ApiOperation(value = "Import the bulk of assets (processAssetsBulkImport)",
notes = "There's an ability to import the bulk of assets using the only .csv file.", produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PostMapping("/asset/bulk_import") @PostMapping("/asset/bulk_import")
public BulkImportResult<Asset> processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception { public BulkImportResult<Asset> processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception {

86
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
@ -48,8 +50,13 @@ import org.thingsboard.server.common.data.security.model.SecuritySettings;
import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy;
import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.model.ActivateUserRequest;
import org.thingsboard.server.service.security.model.ChangePasswordRequest;
import org.thingsboard.server.service.security.model.ResetPasswordEmailRequest;
import org.thingsboard.server.service.security.model.ResetPasswordRequest;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails;
import org.thingsboard.server.service.security.model.JwtTokenPair;
import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@ -74,9 +81,13 @@ public class AuthController extends BaseController {
private final AuditLogService auditLogService; private final AuditLogService auditLogService;
private final ApplicationEventPublisher eventPublisher; private final ApplicationEventPublisher eventPublisher;
@ApiOperation(value = "Get current User (getUser)",
notes = "Get the information about the User which credentials are used to perform this REST API call.")
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/auth/user", method = RequestMethod.GET) @RequestMapping(value = "/auth/user", method = RequestMethod.GET)
public @ResponseBody User getUser() throws ThingsboardException { public @ResponseBody
User getUser() throws ThingsboardException {
try { try {
SecurityUser securityUser = getCurrentUser(); SecurityUser securityUser = getCurrentUser();
return userService.findUserById(securityUser.getTenantId(), securityUser.getId()); return userService.findUserById(securityUser.getTenantId(), securityUser.getId());
@ -85,6 +96,8 @@ public class AuthController extends BaseController {
} }
} }
@ApiOperation(value = "Logout (logout)",
notes = "Special API call to record the 'logout' of the user to the Audit Logs. Since platform uses [JWT](https://jwt.io/), the actual logout is the procedure of clearing the [JWT](https://jwt.io/) token on the client side. ")
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/auth/logout", method = RequestMethod.POST) @RequestMapping(value = "/auth/logout", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ -92,13 +105,17 @@ public class AuthController extends BaseController {
logLogoutAction(request); logLogoutAction(request);
} }
@ApiOperation(value = "Change password for current User (changePassword)",
notes = "Change the password for the User which credentials are used to perform this REST API call. Be aware that previously generated [JWT](https://jwt.io/) tokens will be still valid until they expire.")
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST) @RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public ObjectNode changePassword(@RequestBody JsonNode changePasswordRequest) throws ThingsboardException { public ObjectNode changePassword(
@ApiParam(value = "Change Password Request")
@RequestBody ChangePasswordRequest changePasswordRequest) throws ThingsboardException {
try { try {
String currentPassword = changePasswordRequest.get("currentPassword").asText(); String currentPassword = changePasswordRequest.getCurrentPassword();
String newPassword = changePasswordRequest.get("newPassword").asText(); String newPassword = changePasswordRequest.getNewPassword();
SecurityUser securityUser = getCurrentUser(); SecurityUser securityUser = getCurrentUser();
UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, securityUser.getId()); UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, securityUser.getId());
if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) { if (!passwordEncoder.matches(currentPassword, userCredentials.getPassword())) {
@ -123,6 +140,8 @@ public class AuthController extends BaseController {
} }
} }
@ApiOperation(value = "Get the current User password policy (getUserPasswordPolicy)",
notes = "API call to get the password policy for the password validation form(s).")
@RequestMapping(value = "/noauth/userPasswordPolicy", method = RequestMethod.GET) @RequestMapping(value = "/noauth/userPasswordPolicy", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public UserPasswordPolicy getUserPasswordPolicy() throws ThingsboardException { public UserPasswordPolicy getUserPasswordPolicy() throws ThingsboardException {
@ -135,8 +154,13 @@ public class AuthController extends BaseController {
} }
} }
@ApiOperation(value = "Check Activate User Token (checkActivateToken)",
notes = "Checks the activation token and forwards user to 'Create Password' page. " +
"If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Create Password' page and same 'activateToken' specified in the URL parameters. " +
"If token is not valid, returns '409 Conflict'.")
@RequestMapping(value = "/noauth/activate", params = {"activateToken"}, method = RequestMethod.GET) @RequestMapping(value = "/noauth/activate", params = {"activateToken"}, method = RequestMethod.GET)
public ResponseEntity<String> checkActivateToken( public ResponseEntity<String> checkActivateToken(
@ApiParam(value = "The activate token string.")
@RequestParam(value = "activateToken") String activateToken) { @RequestParam(value = "activateToken") String activateToken) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
HttpStatus responseStatus; HttpStatus responseStatus;
@ -157,13 +181,17 @@ public class AuthController extends BaseController {
return new ResponseEntity<>(headers, responseStatus); return new ResponseEntity<>(headers, responseStatus);
} }
@ApiOperation(value = "Request reset password email (requestResetPasswordByEmail)",
notes = "Request to send the reset password email if the user with specified email address is present in the database. " +
"Always return '200 OK' status for security purposes.")
@RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void requestResetPasswordByEmail( public void requestResetPasswordByEmail(
@RequestBody JsonNode resetPasswordByEmailRequest, @ApiParam(value = "The JSON object representing the reset password email request.")
@RequestBody ResetPasswordEmailRequest resetPasswordByEmailRequest,
HttpServletRequest request) throws ThingsboardException { HttpServletRequest request) throws ThingsboardException {
try { try {
String email = resetPasswordByEmailRequest.get("email").asText(); String email = resetPasswordByEmailRequest.getEmail();
UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email); UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email);
User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId());
String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request);
@ -176,8 +204,13 @@ public class AuthController extends BaseController {
} }
} }
@ApiOperation(value = "Check password reset token (checkResetToken)",
notes = "Checks the password reset token and forwards user to 'Reset Password' page. " +
"If token is valid, returns '303 See Other' (redirect) response code with the correct address of 'Reset Password' page and same 'resetToken' specified in the URL parameters. " +
"If token is not valid, returns '409 Conflict'.")
@RequestMapping(value = "/noauth/resetPassword", params = {"resetToken"}, method = RequestMethod.GET) @RequestMapping(value = "/noauth/resetPassword", params = {"resetToken"}, method = RequestMethod.GET)
public ResponseEntity<String> checkResetToken( public ResponseEntity<String> checkResetToken(
@ApiParam(value = "The reset token string.")
@RequestParam(value = "resetToken") String resetToken) { @RequestParam(value = "resetToken") String resetToken) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
HttpStatus responseStatus; HttpStatus responseStatus;
@ -198,16 +231,24 @@ public class AuthController extends BaseController {
return new ResponseEntity<>(headers, responseStatus); return new ResponseEntity<>(headers, responseStatus);
} }
@ApiOperation(value = "Activate User",
notes = "Checks the activation token and updates corresponding user password in the database. " +
"Now the user may start using his password to login. " +
"The response already contains the [JWT](https://jwt.io) activation and refresh tokens, " +
"to simplify the user activation flow and avoid asking user to input password again after activation. " +
"If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " +
"If token is not valid, returns '404 Bad Request'.")
@RequestMapping(value = "/noauth/activate", method = RequestMethod.POST) @RequestMapping(value = "/noauth/activate", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ResponseBody @ResponseBody
public JsonNode activateUser( public JwtTokenPair activateUser(
@RequestBody JsonNode activateRequest, @ApiParam(value = "Activate user request.")
@RequestBody ActivateUserRequest activateRequest,
@RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
HttpServletRequest request) throws ThingsboardException { HttpServletRequest request) throws ThingsboardException {
try { try {
String activateToken = activateRequest.get("activateToken").asText(); String activateToken = activateRequest.getActivateToken();
String password = activateRequest.get("password").asText(); String password = activateRequest.getPassword();
systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, null); systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, null);
String encodedPassword = passwordEncoder.encode(password); String encodedPassword = passwordEncoder.encode(password);
UserCredentials credentials = userService.activateUserCredentials(TenantId.SYS_TENANT_ID, activateToken, encodedPassword); UserCredentials credentials = userService.activateUserCredentials(TenantId.SYS_TENANT_ID, activateToken, encodedPassword);
@ -232,25 +273,26 @@ public class AuthController extends BaseController {
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
ObjectMapper objectMapper = new ObjectMapper(); return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken());
ObjectNode tokenObject = objectMapper.createObjectNode();
tokenObject.put("token", accessToken.getToken());
tokenObject.put("refreshToken", refreshToken.getToken());
return tokenObject;
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); throw handleException(e);
} }
} }
@ApiOperation(value = "Reset password (resetPassword)",
notes = "Checks the password reset token and updates the password. " +
"If token is valid, returns the object that contains [JWT](https://jwt.io/) access and refresh tokens. " +
"If token is not valid, returns '404 Bad Request'.")
@RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST) @RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
@ResponseBody @ResponseBody
public JsonNode resetPassword( public JwtTokenPair resetPassword(
@RequestBody JsonNode resetPasswordRequest, @ApiParam(value = "Reset password request.")
@RequestBody ResetPasswordRequest resetPasswordRequest,
HttpServletRequest request) throws ThingsboardException { HttpServletRequest request) throws ThingsboardException {
try { try {
String resetToken = resetPasswordRequest.get("resetToken").asText(); String resetToken = resetPasswordRequest.getResetToken();
String password = resetPasswordRequest.get("password").asText(); String password = resetPasswordRequest.getPassword();
UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken); UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken);
if (userCredentials != null) { if (userCredentials != null) {
systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, userCredentials); systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, userCredentials);
@ -273,11 +315,7 @@ public class AuthController extends BaseController {
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
ObjectMapper objectMapper = new ObjectMapper(); return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken());
ObjectNode tokenObject = objectMapper.createObjectNode();
tokenObject.put("token", accessToken.getToken());
tokenObject.put("refreshToken", refreshToken.getToken());
return tokenObject;
} else { } else {
throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} }

42
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -153,6 +153,46 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@TbCoreComponent @TbCoreComponent
public abstract class BaseController { public abstract class BaseController {
/*Swagger UI description*/
public static final String CUSTOMER_ID = "customerId";
public static final String TENANT_ID = "tenantId";
public static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. " +
"The result is wrapped with PageData object that allows you to iterate over result set using pagination. " +
"See the 'Model' tab of the Response Class for more details. ";
public static final String DASHBOARD_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
public static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
public static final String DEVICE_PROFILE_ID_DESCRIPTION = "A string value representing the device profile id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
public static final String TENANT_ID_PARAM_DESCRIPTION = "A string value representing the tenant id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
public static final String EDGE_ID_PARAM_DESCRIPTION = "A string value representing the edge id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
public static final String CUSTOMER_ID_PARAM_DESCRIPTION = "A string value representing the customer id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
public static final String ASSET_ID_PARAM_DESCRIPTION = "A string value representing the asset id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'";
protected final String PAGE_SIZE_DESCRIPTION = "Maximum amount of entities in a one page";
protected final String PAGE_NUMBER_DESCRIPTION = "Sequence number of page starting from 0";
protected final String DEVICE_TYPE_DESCRIPTION = "Device type as the name of the device profile";
protected final String ASSET_TYPE_DESCRIPTION = "Asset type";
protected final String ASSET_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the asset name.";
protected final String DASHBOARD_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the dashboard title.";
protected final String DEVICE_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the device name.";
protected final String CUSTOMER_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the customer title.";
protected final String SORT_PROPERTY_DESCRIPTION = "Property of entity to sort by";
protected final String DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title";
protected final String CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title, email, country, city";
protected final String DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, deviceProfileName, label, customerTitle";
protected final String ASSET_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, type, label, customerTitle";
protected final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESC (DESCENDING)";
protected final String SORT_ORDER_ALLOWABLE_VALUES = "ASC, DESC";
protected final String DEVICE_INFO_DESCRIPTION = "Device Info is an extension of the default Device object that contains information about the assigned customer name and device profile name. ";
protected final String ASSET_INFO_DESCRIPTION = "Asset Info is an extension of the default Asset object that contains information about the assigned customer name. ";
protected final String DEVICE_NAME_DESCRIPTION = "A string value representing the Device name.";
protected final String ASSET_NAME_DESCRIPTION = "A string value representing the Asset name.";
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
protected static final String DEFAULT_DASHBOARD = "defaultDashboardId"; protected static final String DEFAULT_DASHBOARD = "defaultDashboardId";
protected static final String HOME_DASHBOARD = "homeDashboardId"; protected static final String HOME_DASHBOARD = "homeDashboardId";
@ -897,7 +937,7 @@ public abstract class BaseController {
PageDataIterableByTenantIdEntityId<EdgeId> relatedEdgeIdsIterator = PageDataIterableByTenantIdEntityId<EdgeId> relatedEdgeIdsIterator =
new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE); new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE);
List<EdgeId> result = new ArrayList<>(); List<EdgeId> result = new ArrayList<>();
for(EdgeId edgeId : relatedEdgeIdsIterator) { for (EdgeId edgeId : relatedEdgeIdsIterator) {
result.add(edgeId); result.add(edgeId);
} }
return result; return result;

34
application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -38,10 +40,20 @@ import java.util.Set;
@RequestMapping("/api") @RequestMapping("/api")
public class ComponentDescriptorController extends BaseController { public class ComponentDescriptorController extends BaseController {
private static final String COMPONENT_DESCRIPTOR_DEFINITION = "Each Component Descriptor represents configuration of specific rule node (e.g. 'Save Timeseries' or 'Send Email'.). " +
"The Component Descriptors are used by the rule chain Web UI to build the configuration forms for the rule nodes. " +
"The Component Descriptors are discovered at runtime by scanning the class path and searching for @RuleNode annotation. " +
"Once discovered, the up to date list of descriptors is persisted to the database.";
@ApiOperation(value = "Get Component Descriptor (getComponentDescriptorByClazz)",
notes = "Gets the Component Descriptor object using class name from the path parameters. " +
COMPONENT_DESCRIPTOR_DEFINITION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/component/{componentDescriptorClazz:.+}", method = RequestMethod.GET) @RequestMapping(value = "/component/{componentDescriptorClazz:.+}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public ComponentDescriptor getComponentDescriptorByClazz(@PathVariable("componentDescriptorClazz") String strComponentDescriptorClazz) throws ThingsboardException { public ComponentDescriptor getComponentDescriptorByClazz(
@ApiParam(value = "Component Descriptor class name", required = true)
@PathVariable("componentDescriptorClazz") String strComponentDescriptorClazz) throws ThingsboardException {
checkParameter("strComponentDescriptorClazz", strComponentDescriptorClazz); checkParameter("strComponentDescriptorClazz", strComponentDescriptorClazz);
try { try {
return checkComponentDescriptorByClazz(strComponentDescriptorClazz); return checkComponentDescriptorByClazz(strComponentDescriptorClazz);
@ -50,11 +62,17 @@ public class ComponentDescriptorController extends BaseController {
} }
} }
@ApiOperation(value = "Get Component Descriptors (getComponentDescriptorsByType)",
notes = "Gets the Component Descriptors using rule node type and optional rule chain type request parameters. " +
COMPONENT_DESCRIPTOR_DEFINITION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET) @RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<ComponentDescriptor> getComponentDescriptorsByType(@PathVariable("componentType") String strComponentType, public List<ComponentDescriptor> getComponentDescriptorsByType(
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { @ApiParam(value = "Type of the Rule Node", allowableValues = "ENRICHMENT,FILTER,TRANSFORMATION,ACTION,EXTERNAL", required = true)
@PathVariable("componentType") String strComponentType,
@ApiParam(value = "Type of the Rule Chain", allowableValues = "CORE,EDGE")
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
checkParameter("componentType", strComponentType); checkParameter("componentType", strComponentType);
try { try {
return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType)); return checkComponentDescriptorsByType(ComponentType.valueOf(strComponentType), getRuleChainType(strRuleChainType));
@ -63,11 +81,17 @@ public class ComponentDescriptorController extends BaseController {
} }
} }
@ApiOperation(value = "Get Component Descriptors (getComponentDescriptorsByTypes)",
notes = "Gets the Component Descriptors using coma separated list of rule node types and optional rule chain type request parameters. " +
COMPONENT_DESCRIPTOR_DEFINITION)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN','TENANT_ADMIN')")
@RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET) @RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<ComponentDescriptor> getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes, public List<ComponentDescriptor> getComponentDescriptorsByTypes(
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException { @ApiParam(value = "List of types of the Rule Nodes, (ENRICHMENT, FILTER, TRANSFORMATION, ACTION or EXTERNAL)", required = true)
@RequestParam("componentTypes") String[] strComponentTypes,
@ApiParam(value = "Type of the Rule Chain", allowableValues = "CORE,EDGE")
@RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
checkArrayParameter("componentTypes", strComponentTypes); checkArrayParameter("componentTypes", strComponentTypes);
try { try {
Set<ComponentType> componentTypes = new HashSet<>(); Set<ComponentType> componentTypes = new HashSet<>();

60
application/src/main/java/org/thingsboard/server/controller/CustomerController.java

@ -18,6 +18,8 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -50,18 +52,23 @@ import java.util.List;
@RequestMapping("/api") @RequestMapping("/api")
public class CustomerController extends BaseController { public class CustomerController extends BaseController {
public static final String CUSTOMER_ID = "customerId";
public static final String IS_PUBLIC = "isPublic"; public static final String IS_PUBLIC = "isPublic";
public static final String CUSTOMER_SECURITY_CHECK = "If the user has the authority of 'Tenant Administrator', the server checks that the customer is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the user belongs to the customer.";
@ApiOperation(value = "Get Customer (getCustomerById)",
notes = "Get the Customer object based on the provided Customer Id. " + CUSTOMER_SECURITY_CHECK)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}", method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Customer getCustomerById(@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { public Customer getCustomerById(
@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId); checkParameter(CUSTOMER_ID, strCustomerId);
try { try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId, Operation.READ); Customer customer = checkCustomerId(customerId, Operation.READ);
if(!customer.getAdditionalInfo().isNull()) { if (!customer.getAdditionalInfo().isNull()) {
processDashboardIdFromAdditionalInfo((ObjectNode) customer.getAdditionalInfo(), HOME_DASHBOARD); processDashboardIdFromAdditionalInfo((ObjectNode) customer.getAdditionalInfo(), HOME_DASHBOARD);
} }
return customer; return customer;
@ -70,10 +77,15 @@ public class CustomerController extends BaseController {
} }
} }
@ApiOperation(value = "Get short Customer info (getShortCustomerInfoById)",
notes = "Get the short customer object that contains only the title and 'isPublic' flag. " + CUSTOMER_SECURITY_CHECK)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/shortInfo", method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/shortInfo", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public JsonNode getShortCustomerInfoById(@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { public JsonNode getShortCustomerInfoById(
@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId); checkParameter(CUSTOMER_ID, strCustomerId);
try { try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
@ -88,10 +100,14 @@ public class CustomerController extends BaseController {
} }
} }
@ApiOperation(value = "Get Customer Title (getCustomerTitleById)",
notes = "Get the title of the customer. " + CUSTOMER_SECURITY_CHECK)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/title", method = RequestMethod.GET, produces = "application/text") @RequestMapping(value = "/customer/{customerId}/title", method = RequestMethod.GET, produces = "application/text")
@ResponseBody @ResponseBody
public String getCustomerTitleById(@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { public String getCustomerTitleById(
@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId); checkParameter(CUSTOMER_ID, strCustomerId);
try { try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
@ -102,10 +118,15 @@ public class CustomerController extends BaseController {
} }
} }
@ApiOperation(value = "Create or update Customer (saveCustomer)",
notes = "Creates or Updates the Customer. When creating customer, platform generates Customer Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address) " +
"The newly created Customer Id will be present in the response. " +
"Specify existing Customer Id to update the Customer. " +
"Referencing non-existing Customer Id will cause 'Not Found' error.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer", method = RequestMethod.POST) @RequestMapping(value = "/customer", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException { public Customer saveCustomer(@ApiParam(value = "A JSON value representing the customer.") @RequestBody Customer customer) throws ThingsboardException {
try { try {
customer.setTenantId(getCurrentUser().getTenantId()); customer.setTenantId(getCurrentUser().getTenantId());
@ -131,10 +152,13 @@ public class CustomerController extends BaseController {
} }
} }
@ApiOperation(value = "Delete Customer (deleteCustomer)",
notes = "Deletes the Customer and all customer Users. All assigned Dashboards, Assets, Devices, etc. will be unassigned but not deleted. Referencing non-existing Customer Id will cause an error.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/{customerId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void deleteCustomer(@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException { public void deleteCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable(CUSTOMER_ID) String strCustomerId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId); checkParameter(CUSTOMER_ID, strCustomerId);
try { try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
@ -161,14 +185,23 @@ public class CustomerController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Customers (getCustomers)",
notes = "Returns a page of customers owned by tenant. " +
PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customers", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customers", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Customer> getCustomers(@RequestParam int pageSize, public PageData<Customer> getCustomers(
@RequestParam int page, @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam(required = false) String textSearch, @RequestParam int pageSize,
@RequestParam(required = false) String sortProperty, @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam int page,
@ApiParam(value = CUSTOMER_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
@ -178,10 +211,13 @@ public class CustomerController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Customer by Customer title (getTenantCustomer)",
notes = "Get the Customer using Customer Title. Available for users with 'Tenant Administrator' authority only.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/customers", params = {"customerTitle"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/customers", params = {"customerTitle"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Customer getTenantCustomer( public Customer getTenantCustomer(
@ApiParam(value = "A string value representing the Customer title.")
@RequestParam String customerTitle) throws ThingsboardException { @RequestParam String customerTitle) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();

202
application/src/main/java/org/thingsboard/server/controller/DashboardController.java

@ -17,8 +17,11 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
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.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -68,11 +71,16 @@ public class DashboardController extends BaseController {
private static final String HOME_DASHBOARD_ID = "homeDashboardId"; private static final String HOME_DASHBOARD_ID = "homeDashboardId";
private static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar"; private static final String HOME_DASHBOARD_HIDE_TOOLBAR = "homeDashboardHideToolbar";
public static final String DASHBOARD_INFO_DEFINITION = "The Dashboard Info object contains lightweight information about the dashboard (e.g. title, image, assigned customers) but does not contain the heavyweight configuration JSON.";
public static final String DASHBOARD_DEFINITION = "The Dashboard object is a heavyweight object that contains information about the dashboard (e.g. title, image, assigned customers) and also configuration JSON (e.g. layouts, widgets, entity aliases).";
public static final String HIDDEN_FOR_MOBILE = "Exclude dashboards that are hidden for mobile";
@Value("${dashboard.max_datapoints_limit}") @Value("${dashboard.max_datapoints_limit}")
private long maxDatapointsLimit; private long maxDatapointsLimit;
@ApiOperation(value = "Get server time (getServerTime)",
notes = "Get the server time (milliseconds since January 1, 1970 UTC). " +
"Used to adjust view of the dashboards according to the difference between browser and server time.")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -80,6 +88,11 @@ public class DashboardController extends BaseController {
return System.currentTimeMillis(); return System.currentTimeMillis();
} }
@ApiOperation(value = "Get max data points limit (getMaxDatapointsLimit)",
notes = "Get the maximum number of data points that dashboard may request from the server per in a single subscription command. " +
"This value impacts the time window behavior. It impacts 'Max values' parameter in case user selects 'None' as 'Data aggregation function'. " +
"It also impacts the 'Grouping interval' in case of any other 'Data aggregation function' is selected. " +
"The actual value of the limit is configurable in the system configuration file.")
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -87,10 +100,16 @@ public class DashboardController extends BaseController {
return maxDatapointsLimit; return maxDatapointsLimit;
} }
@ApiOperation(value = "Get Dashboard Info (getDashboardInfoById)",
notes = "Get the information about the dashboard based on 'dashboardId' parameter. " + DASHBOARD_INFO_DEFINITION,
produces = MediaType.APPLICATION_JSON_VALUE
)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public DashboardInfo getDashboardInfoById(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { public DashboardInfo getDashboardInfoById(
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
@ -100,10 +119,16 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Get Dashboard (getDashboardById)",
notes = "Get the dashboard based on 'dashboardId' parameter. " + DASHBOARD_DEFINITION,
produces = MediaType.APPLICATION_JSON_VALUE
)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Dashboard getDashboardById(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { public Dashboard getDashboardById(
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
@ -113,10 +138,20 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Create Or Update Dashboard (saveDashboard)",
notes = "Create or update the Dashboard. When creating dashboard, platform generates Dashboard Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address)." +
"The newly created Dashboard id will be present in the response. " +
"Specify existing Dashboard id to update the dashboard. " +
"Referencing non-existing dashboard Id will cause 'Not Found' error. " +
"Only users with 'TENANT_ADMIN') authority may create the dashboards.",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard", method = RequestMethod.POST) @RequestMapping(value = "/dashboard", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard saveDashboard(@RequestBody Dashboard dashboard) throws ThingsboardException { public Dashboard saveDashboard(
@ApiParam(value = "A JSON value representing the dashboard.")
@RequestBody Dashboard dashboard) throws ThingsboardException {
try { try {
dashboard.setTenantId(getCurrentUser().getTenantId()); dashboard.setTenantId(getCurrentUser().getTenantId());
@ -141,10 +176,14 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Delete the Dashboard (deleteDashboard)",
notes = "Delete the Dashboard. Only users with 'TENANT_ADMIN') authority may delete the dashboards.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void deleteDashboard(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { public void deleteDashboard(
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
@ -170,12 +209,19 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Assign the Dashboard (assignDashboardToCustomer)",
notes = "Assign the Dashboard to specified Customer or do nothing if the Dashboard is already assigned to that Customer. " +
"Returns the Dashboard object. Only users with 'TENANT_ADMIN') authority may assign the dashboards to customers.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard assignDashboardToCustomer(@PathVariable("customerId") String strCustomerId, public Dashboard assignDashboardToCustomer(
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
checkParameter("customerId", strCustomerId); @PathVariable(CUSTOMER_ID) String strCustomerId,
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(CUSTOMER_ID, strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
@ -203,11 +249,18 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Unassign the Dashboard (unassignDashboardFromCustomer)",
notes = "Unassign the Dashboard from specified Customer or do nothing if the Dashboard is already assigned to that Customer. " +
"Returns the Dashboard object. Only users with 'TENANT_ADMIN') authority may unassign the dashboards from customers.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId, public Dashboard unassignDashboardFromCustomer(
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable(CUSTOMER_ID) String strCustomerId,
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
@ -235,11 +288,20 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Update the Dashboard Customers (updateDashboardCustomers)",
notes = "Updates the list of Customers that this Dashboard is assigned to. Removes previous assignments to customers that are not in the provided list. " +
"Returns the Dashboard object. Only users with 'TENANT_ADMIN') authority may assign the dashboards to customers.",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST) @RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard updateDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId, public Dashboard updateDashboardCustomers(
@RequestBody(required = false) String[] strCustomerIds) throws ThingsboardException { @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId,
@ApiParam(value = "JSON array with the list of customer ids, or empty to remove all customers")
@RequestBody(required = false) String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
@ -301,11 +363,19 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Adds the Dashboard Customers (addDashboardCustomers)",
notes = "Adds the list of Customers to the existing list of assignments for the Dashboard. Keeps previous assignments to customers that are not in the provided list. " +
"Returns the Dashboard object. Only users with 'TENANT_ADMIN') authority may assign the dashboards to customers.",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST) @RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard addDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId, public Dashboard addDashboardCustomers(
@RequestBody String[] strCustomerIds) throws ThingsboardException { @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId,
@ApiParam(value = "JSON array with the list of customer ids")
@RequestBody String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
@ -345,11 +415,19 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Remove the Dashboard Customers (removeDashboardCustomers)",
notes = "Removes the list of Customers from the existing list of assignments for the Dashboard. Keeps other assignments to customers that are not in the provided list. " +
"Returns the Dashboard object. Only users with 'TENANT_ADMIN') authority may assign the dashboards to customers.",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST) @RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard removeDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId, public Dashboard removeDashboardCustomers(
@RequestBody String[] strCustomerIds) throws ThingsboardException { @ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId,
@ApiParam(value = "JSON array with the list of customer ids")
@RequestBody String[] strCustomerIds) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
@ -389,10 +467,20 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Assign the Dashboard to Public Customer (assignDashboardToPublicCustomer)",
notes = "Assigns the dashboard to a special, auto-generated 'Public' Customer. Once assigned, unauthenticated users may browse the dashboard. " +
"This method is useful if you like to embed the dashboard on public web pages to be available for users that are not logged in. " +
"Be aware that making the dashboard public does not mean that it automatically makes all devices and assets you use in the dashboard to be public." +
"Use [assign Asset to Public Customer](#!/asset-controller/assignAssetToPublicCustomerUsingPOST) and " +
"[assign Device to Public Customer](#!/device-controller/assignDeviceToPublicCustomerUsingPOST) for this purpose. " +
"Returns the Dashboard object. Only users with 'TENANT_ADMIN') authority may assign the dashboards to customers.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Dashboard assignDashboardToPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { public Dashboard assignDashboardToPublicCustomer(
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
@ -415,10 +503,16 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Unassign the Dashboard from Public Customer (unassignDashboardFromPublicCustomer)",
notes = "Unassigns the dashboard from a special, auto-generated 'Public' Customer. Once unassigned, unauthenticated users may no longer browse the dashboard. " +
"Returns the Dashboard object. Only users with 'TENANT_ADMIN') authority may assign the dashboards to customers.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Dashboard unassignDashboardFromPublicCustomer(@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException { public Dashboard unassignDashboardFromPublicCustomer(
@ApiParam(value = DASHBOARD_ID_PARAM_DESCRIPTION)
@PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
checkParameter(DASHBOARD_ID, strDashboardId); checkParameter(DASHBOARD_ID, strDashboardId);
try { try {
DashboardId dashboardId = new DashboardId(toUUID(strDashboardId)); DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
@ -442,15 +536,25 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Dashboards by System Administrator (getTenantDashboards)",
notes = "Returns a page of dashboard info objects owned by tenant. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS +
"Only users with 'SYS_ADMIN' authority may use this method.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('SYS_ADMIN')") @PreAuthorize("hasAuthority('SYS_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<DashboardInfo> getTenantDashboards( public PageData<DashboardInfo> getTenantDashboards(
@PathVariable("tenantId") String strTenantId, @ApiParam(value = TENANT_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(TENANT_ID) String strTenantId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = new TenantId(toUUID(strTenantId));
@ -462,20 +566,30 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Dashboards (getTenantDashboards)",
notes = "Returns a page of dashboard info objects owned by the tenant of a current user. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS +
"Only users with 'TENANT_ADMIN' authority may use this method.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<DashboardInfo> getTenantDashboards( public PageData<DashboardInfo> getTenantDashboards(
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = HIDDEN_FOR_MOBILE)
@RequestParam(required = false) Boolean mobile, @RequestParam(required = false) Boolean mobile,
@ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (mobile != null && mobile.booleanValue()) { if (mobile != null && mobile) {
return checkNotNull(dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink)); return checkNotNull(dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink));
} else { } else {
return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink)); return checkNotNull(dashboardService.findDashboardsByTenantId(tenantId, pageLink));
@ -485,24 +599,35 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Get Customer Dashboards (getCustomerDashboards)",
notes = "Returns a page of dashboard info objects owned by the specified customer. " + DASHBOARD_INFO_DEFINITION + " " + PAGE_DATA_PARAMETERS +
"Only users with 'TENANT_ADMIN' or 'CUSTOMER_USER' authority may use this method.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<DashboardInfo> getCustomerDashboards( public PageData<DashboardInfo> getCustomerDashboards(
@PathVariable("customerId") String strCustomerId, @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(CUSTOMER_ID) String strCustomerId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = HIDDEN_FOR_MOBILE)
@RequestParam(required = false) Boolean mobile, @RequestParam(required = false) Boolean mobile,
@ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter(CUSTOMER_ID, strCustomerId);
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
CustomerId customerId = new CustomerId(toUUID(strCustomerId)); CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId, Operation.READ); checkCustomerId(customerId, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
if (mobile != null && mobile.booleanValue()) { if (mobile != null && mobile) {
return checkNotNull(dashboardService.findMobileDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); return checkNotNull(dashboardService.findMobileDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
} else { } else {
return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink)); return checkNotNull(dashboardService.findDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
@ -512,6 +637,13 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Get Home Dashboard (getHomeDashboard)",
notes = "Returns the home dashboard object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " +
"If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " +
"If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. "
+ DASHBOARD_DEFINITION + " " +
"Only users with 'TENANT_ADMIN' or 'CUSTOMER_USER' authority should use this method.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/dashboard/home", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/home", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -543,6 +675,12 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Get Home Dashboard Info (getHomeDashboardInfo)",
notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the User. " +
"If 'homeDashboardId' parameter is not set on the User level and the User has authority 'CUSTOMER_USER', check the same parameter for the corresponding Customer. " +
"If 'homeDashboardId' parameter is not set on the User and Customer levels then checks the same parameter for the Tenant that owns the user. " +
"Only users with 'TENANT_ADMIN' or 'CUSTOMER_USER' authority should use this method.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET) @RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -574,6 +712,10 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Home Dashboard Info (getTenantHomeDashboardInfo)",
notes = "Returns the home dashboard info object that is configured as 'homeDashboardId' parameter in the 'additionalInfo' of the corresponding tenant. " +
"Only users with 'TENANT_ADMIN' authority may use this method.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET) @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -596,10 +738,16 @@ public class DashboardController extends BaseController {
} }
} }
@ApiOperation(value = "Update Tenant Home Dashboard Info (getTenantHomeDashboardInfo)",
notes = "Update the home dashboard assignment for the current tenant. " +
"Only users with 'TENANT_ADMIN' authority may use this method.",
produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST) @RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void setTenantHomeDashboardInfo(@RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException { public void setTenantHomeDashboardInfo(
@ApiParam(value = "A JSON object that represents home dashboard id and other parameters", required = true)
@RequestBody HomeDashboardInfo homeDashboardInfo) throws ThingsboardException {
try { try {
if (homeDashboardInfo.getDashboardId() != null) { if (homeDashboardInfo.getDashboardId() != null) {
checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ); checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ);
@ -635,7 +783,8 @@ public class DashboardController extends BaseController {
} }
return new HomeDashboardInfo(dashboardId, hideDashboardToolbar); return new HomeDashboardInfo(dashboardId, hideDashboardToolbar);
} }
} catch (Exception e) {} } catch (Exception e) {
}
return null; return null;
} }
@ -651,7 +800,8 @@ public class DashboardController extends BaseController {
} }
return new HomeDashboard(dashboard, hideDashboardToolbar); return new HomeDashboard(dashboard, hideDashboardToolbar);
} }
} catch (Exception e) {} } catch (Exception e) {
}
return null; return null;
} }

187
application/src/main/java/org/thingsboard/server/controller/DeviceController.java

@ -19,6 +19,8 @@ import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -91,16 +93,22 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
public class DeviceController extends BaseController { public class DeviceController extends BaseController {
private final DeviceBulkImportService deviceBulkImportService;
private static final String DEVICE_ID = "deviceId"; protected static final String DEVICE_ID = "deviceId";
private static final String DEVICE_NAME = "deviceName"; protected static final String DEVICE_NAME = "deviceName";
private static final String TENANT_ID = "tenantId"; protected static final String TENANT_ID = "tenantId";
private final DeviceBulkImportService deviceBulkImportService;
@ApiOperation(value = "Get Device (getDeviceById)",
notes = "Fetch the Device object based on the provided Device Id. " +
"If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET) @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Device getDeviceById(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { public Device getDeviceById(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
try { try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
@ -110,10 +118,15 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Device Info (getDeviceInfoById)",
notes = "Fetch the Device Info object based on the provided Device Id. " +
"If the user has the authority of 'Tenant Administrator', the server checks that the device is owned by the same tenant. " +
"If the user has the authority of 'Customer User', the server checks that the device is assigned to the same customer. " + DEVICE_INFO_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/info/{deviceId}", method = RequestMethod.GET) @RequestMapping(value = "/device/info/{deviceId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public DeviceInfo getDeviceInfoById(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { public DeviceInfo getDeviceInfoById(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
try { try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
@ -123,11 +136,18 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Create Or Update Device (saveDevice)",
notes = "Create or update the Device. When creating device, platform generates Device Id as [time-based UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_1_(date-time_and_MAC_address). " +
"Device credentials are also generated if not provided in the 'accessToken' request parameter. " +
"The newly created device id will be present in the response. " +
"Specify existing Device id to update the device. " +
"Referencing non-existing device Id will cause 'Not Found' error.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device", method = RequestMethod.POST) @RequestMapping(value = "/device", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Device saveDevice(@RequestBody Device device, public Device saveDevice(@ApiParam(value = "A JSON value representing the device.") @RequestBody Device device,
@RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException { @ApiParam(value = "Optional value of the device credentials to be used during device creation. " +
"If omitted, access token will be auto-generated.") @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException {
boolean created = device.getId() == null; boolean created = device.getId() == null;
try { try {
device.setTenantId(getCurrentUser().getTenantId()); device.setTenantId(getCurrentUser().getTenantId());
@ -164,10 +184,13 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Delete device (deleteDevice)",
notes = "Deletes the device, it's credentials and all the relations (from and to the device). Referencing non-existing device Id will cause an error.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE) @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void deleteDevice(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { public void deleteDevice(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
try { try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
@ -193,10 +216,14 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Assign device to customer (assignDeviceToCustomer)",
notes = "Creates assignment of the device to customer. Customer will be able to query device afterwards.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/{customerId}/device/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/{customerId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Device assignDeviceToCustomer(@PathVariable("customerId") String strCustomerId, public Device assignDeviceToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable("customerId") String strCustomerId,
@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
@ -225,10 +252,13 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Unassign device from customer (unassignDeviceFromCustomer)",
notes = "Clears assignment of the device to customer. Customer will not be able to query device afterwards.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/device/{deviceId}", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/device/{deviceId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Device unassignDeviceFromCustomer(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { public Device unassignDeviceFromCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
try { try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
@ -256,10 +286,15 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Make device publicly available (assignDeviceToPublicCustomer)",
notes = "Device will be available for non-authorized (not logged-in) users. " +
"This is useful to create dashboards that you plan to share/embed on a publicly available website. " +
"However, users that are logged-in and belong to different tenant will not be able to access the device.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/customer/public/device/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/customer/public/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Device assignDeviceToPublicCustomer(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { public Device assignDeviceToPublicCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
try { try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
@ -280,10 +315,13 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Device Credentials (getDeviceCredentialsByDeviceId)",
notes = "If during device creation there wasn't specified any credentials, platform generates random 'ACCESS_TOKEN' credentials.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET) @RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public DeviceCredentials getDeviceCredentialsByDeviceId(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { public DeviceCredentials getDeviceCredentialsByDeviceId(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
try { try {
DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
@ -301,10 +339,16 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Update device credentials (updateDeviceCredentials)", notes = "During device creation, platform generates random 'ACCESS_TOKEN' credentials. " +
"Use this method to update the device credentials. First use 'getDeviceCredentialsByDeviceId' to get the credentials id and value. " +
"Then use current method to update the credentials type and value. It is not possible to create multiple device credentials for the same device. " +
"The structure of device credentials id and value is simple for the 'ACCESS_TOKEN' but is much more complex for the 'MQTT_BASIC' or 'LWM2M_CREDENTIALS'.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/device/credentials", method = RequestMethod.POST) @RequestMapping(value = "/device/credentials", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public DeviceCredentials saveDeviceCredentials(@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { public DeviceCredentials updateDeviceCredentials(
@ApiParam(value = "A JSON value representing the device credentials.")
@RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException {
checkNotNull(deviceCredentials); checkNotNull(deviceCredentials);
try { try {
Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS); Device device = checkDeviceId(deviceCredentials.getDeviceId(), Operation.WRITE_CREDENTIALS);
@ -325,15 +369,24 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Devices (getTenantDevices)",
notes = "Returns a page of devices owned by tenant. " +
PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Device> getTenantDevices( public PageData<Device> getTenantDevices(
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
@ -348,17 +401,28 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Device Infos (getTenantDeviceInfos)",
notes = "Returns a page of devices info objects owned by tenant. " +
PAGE_DATA_PARAMETERS + DEVICE_INFO_DESCRIPTION)
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<DeviceInfo> getTenantDeviceInfos( public PageData<DeviceInfo> getTenantDeviceInfos(
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = DEVICE_PROFILE_ID_DESCRIPTION)
@RequestParam(required = false) String deviceProfileId, @RequestParam(required = false) String deviceProfileId,
@ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder
) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
@ -375,10 +439,14 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Tenant Device (getTenantDevice)",
notes = "Requested device must be owned by tenant that the user belongs to. " +
"Device name is an unique property of device. So it can be used to identify the device.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET) @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Device getTenantDevice( public Device getTenantDevice(
@ApiParam(value = DEVICE_NAME_DESCRIPTION)
@RequestParam String deviceName) throws ThingsboardException { @RequestParam String deviceName) throws ThingsboardException {
try { try {
TenantId tenantId = getCurrentUser().getTenantId(); TenantId tenantId = getCurrentUser().getTenantId();
@ -388,16 +456,26 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Customer Devices (getCustomerDevices)",
notes = "Returns a page of devices objects assigned to customer. " +
PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Device> getCustomerDevices( public PageData<Device> getCustomerDevices(
@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
try { try {
@ -415,17 +493,28 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Customer Device Infos (getCustomerDeviceInfos)",
notes = "Returns a page of devices info objects assigned to customer. " +
PAGE_DATA_PARAMETERS + DEVICE_INFO_DESCRIPTION)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<DeviceInfo> getCustomerDeviceInfos( public PageData<DeviceInfo> getCustomerDeviceInfos(
@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("customerId") String strCustomerId, @PathVariable("customerId") String strCustomerId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = DEVICE_PROFILE_ID_DESCRIPTION)
@RequestParam(required = false) String deviceProfileId, @RequestParam(required = false) String deviceProfileId,
@ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder) throws ThingsboardException { @RequestParam(required = false) String sortOrder) throws ThingsboardException {
checkParameter("customerId", strCustomerId); checkParameter("customerId", strCustomerId);
try { try {
@ -446,10 +535,13 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Devices By Ids (getDevicesByIds)",
notes = "Requested devices must be owned by tenant or assigned to customer which user is performing the request. ")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET) @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<Device> getDevicesByIds( public List<Device> getDevicesByIds(
@ApiParam(value = "A list of devices ids, separated by comma ','")
@RequestParam("deviceIds") String[] strDeviceIds) throws ThingsboardException { @RequestParam("deviceIds") String[] strDeviceIds) throws ThingsboardException {
checkArrayParameter("deviceIds", strDeviceIds); checkArrayParameter("deviceIds", strDeviceIds);
try { try {
@ -472,6 +564,10 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Find related devices (findByQuery)",
notes = "Returns all devices that are related to the specific entity. " +
"The entity id, relation type, device types, depth of the search, and other query parameters defined using complex 'DeviceSearchQuery' object. " +
"See 'Model' tab of the Parameters for more info.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/devices", method = RequestMethod.POST) @RequestMapping(value = "/devices", method = RequestMethod.POST)
@ResponseBody @ResponseBody
@ -496,6 +592,8 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get Device Types (getDeviceTypes)",
notes = "Returns a set of unique device profile names based on devices that are either owned by the tenant or assigned to the customer which user is performing the request.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/device/types", method = RequestMethod.GET) @RequestMapping(value = "/device/types", method = RequestMethod.GET)
@ResponseBody @ResponseBody
@ -510,10 +608,19 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Claim device (claimDevice)",
notes = "Claiming makes it possible to assign a device to the specific customer using device/server side claiming data (in the form of secret key)." +
"To make this happen you have to provide unique device name and optional claiming data (it is needed only for device-side claiming)." +
"Once device is claimed, the customer becomes its owner and customer users may access device data as well as control the device. \n" +
"In order to enable claiming devices feature a system parameter security.claim.allowClaimingByDefault should be set to true, " +
"otherwise a server-side claimingAllowed attribute with the value true is obligatory for provisioned devices. \n" +
"See official documentation for more details regarding claiming.")
@PreAuthorize("hasAuthority('CUSTOMER_USER')") @PreAuthorize("hasAuthority('CUSTOMER_USER')")
@RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.POST) @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public DeferredResult<ResponseEntity> claimDevice(@PathVariable(DEVICE_NAME) String deviceName, public DeferredResult<ResponseEntity> claimDevice(@ApiParam(value = "Unique name of the device which is going to be claimed")
@PathVariable(DEVICE_NAME) String deviceName,
@ApiParam(value = "Claiming request which can optionally contain secret key")
@RequestBody(required = false) ClaimRequest claimRequest) throws ThingsboardException { @RequestBody(required = false) ClaimRequest claimRequest) throws ThingsboardException {
checkParameter(DEVICE_NAME, deviceName); checkParameter(DEVICE_NAME, deviceName);
try { try {
@ -564,10 +671,13 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Reclaim device (reClaimDevice)",
notes = "Reclaiming means the device will be unassigned from the customer and the device will be available for claiming again.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.DELETE) @RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public DeferredResult<ResponseEntity> reClaimDevice(@PathVariable(DEVICE_NAME) String deviceName) throws ThingsboardException { public DeferredResult<ResponseEntity> reClaimDevice(@ApiParam(value = "Unique name of the device which is going to be reclaimed")
@PathVariable(DEVICE_NAME) String deviceName) throws ThingsboardException {
checkParameter(DEVICE_NAME, deviceName); checkParameter(DEVICE_NAME, deviceName);
try { try {
final DeferredResult<ResponseEntity> deferredResult = new DeferredResult<>(); final DeferredResult<ResponseEntity> deferredResult = new DeferredResult<>();
@ -615,10 +725,14 @@ public class DeviceController extends BaseController {
return DataConstants.DEFAULT_SECRET_KEY; return DataConstants.DEFAULT_SECRET_KEY;
} }
@ApiOperation(value = "Assign device to tenant (assignDeviceToTenant)",
notes = "Creates assignment of the device to tenant. Thereafter tenant will be able to reassign the device to a customer.")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Device assignDeviceToTenant(@PathVariable(TENANT_ID) String strTenantId, public Device assignDeviceToTenant(@ApiParam(value = TENANT_ID_PARAM_DESCRIPTION)
@PathVariable(TENANT_ID) String strTenantId,
@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(TENANT_ID, strTenantId); checkParameter(TENANT_ID, strTenantId);
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
@ -665,10 +779,16 @@ public class DeviceController extends BaseController {
return metaData; return metaData;
} }
@ApiOperation(value = "Assign device to edge (assignDeviceToEdge)",
notes = "Creates assignment of an existing device to an instance of The Edge. " +
"The Edge is a software product for edge computing. " +
"It allows bringing data analysis and management to the edge, while seamlessly synchronizing with the platform server (cloud). ")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST) @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Device assignDeviceToEdge(@PathVariable(EDGE_ID) String strEdgeId, public Device assignDeviceToEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION)
@PathVariable(EDGE_ID) String strEdgeId,
@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
@ -699,10 +819,14 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Unassign device from edge (unassignDeviceFromEdge)",
notes = "Clears assignment of the device to the edge")
@PreAuthorize("hasAuthority('TENANT_ADMIN')") @PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE) @RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE)
@ResponseBody @ResponseBody
public Device unassignDeviceFromEdge(@PathVariable(EDGE_ID) String strEdgeId, public Device unassignDeviceFromEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION)
@PathVariable(EDGE_ID) String strEdgeId,
@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION)
@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
checkParameter(DEVICE_ID, strDeviceId); checkParameter(DEVICE_ID, strDeviceId);
@ -733,18 +857,30 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Get devices assigned to edge (getEdgeDevices)",
notes = "Returns a page of devices assigned to edge. " +
PAGE_DATA_PARAMETERS)
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public PageData<Device> getEdgeDevices( public PageData<Device> getEdgeDevices(
@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId, @PathVariable(EDGE_ID) String strEdgeId,
@ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize, @RequestParam int pageSize,
@ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page, @RequestParam int page,
@ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type, @RequestParam(required = false) String type,
@ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch, @RequestParam(required = false) String textSearch,
@ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty, @RequestParam(required = false) String sortProperty,
@ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder, @RequestParam(required = false) String sortOrder,
@ApiParam(value = "Timestamp. Devices with creation time before it won't be queried")
@RequestParam(required = false) Long startTime, @RequestParam(required = false) Long startTime,
@ApiParam(value = "Timestamp. Devices with creation time after it won't be queried")
@RequestParam(required = false) Long endTime) throws ThingsboardException { @RequestParam(required = false) Long endTime) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId); checkParameter(EDGE_ID, strEdgeId);
try { try {
@ -776,10 +912,17 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Count devices by device profile (countByDeviceProfileAndEmptyOtaPackage)",
notes = "The platform gives an ability to load OTA (over-the-air) packages to devices. " +
"It can be done in two different ways: device scope or device profile scope." +
"In the response you will find the number of devices with specified device profile, but without previously defined device scope OTA package. " +
"It can be useful when you want to define number of devices that will be affected with future OTA package")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET) @RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Long countByDeviceProfileAndEmptyOtaPackage(@PathVariable("otaPackageType") String otaPackageType, public Long countByDeviceProfileAndEmptyOtaPackage(@ApiParam(value = "OTA package type", allowableValues = "FIRMWARE, SOFTWARE")
@PathVariable("otaPackageType") String otaPackageType,
@ApiParam(value = "Device Profile Id. I.g. '784f394c-42b6-435a-983c-b7beff2784f9'")
@PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException { @PathVariable("deviceProfileId") String deviceProfileId) throws ThingsboardException {
checkParameter("OtaPackageType", otaPackageType); checkParameter("OtaPackageType", otaPackageType);
checkParameter("DeviceProfileId", deviceProfileId); checkParameter("DeviceProfileId", deviceProfileId);
@ -793,6 +936,8 @@ public class DeviceController extends BaseController {
} }
} }
@ApiOperation(value = "Import the bulk of devices (processDevicesBulkImport)",
notes = "There's an ability to import the bulk of devices using the only .csv file.")
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@PostMapping("/device/bulk_import") @PostMapping("/device/bulk_import")
public BulkImportResult<Device> processDevicesBulkImport(@RequestBody BulkImportRequest request) throws Exception { public BulkImportResult<Device> processDevicesBulkImport(@RequestBody BulkImportRequest request) throws Exception {

3
application/src/main/java/org/thingsboard/server/controller/EntityViewController.java

@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.EntityViewInfo; import org.thingsboard.server.common.data.EntityViewInfo;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventActionType;
@ -49,7 +48,6 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
@ -74,7 +72,6 @@ import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.thingsboard.server.controller.CustomerController.CUSTOMER_ID;
import static org.thingsboard.server.controller.EdgeController.EDGE_ID; import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
/** /**

4
application/src/main/java/org/thingsboard/server/controller/TenantController.java

@ -57,7 +57,7 @@ public class TenantController extends BaseController {
@RequestMapping(value = "/tenant/{tenantId}", method = RequestMethod.GET) @RequestMapping(value = "/tenant/{tenantId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Tenant getTenantById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException { public Tenant getTenantById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
checkParameter("tenantId", strTenantId); checkParameter(TENANT_ID, strTenantId);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = new TenantId(toUUID(strTenantId));
Tenant tenant = checkTenantId(tenantId, Operation.READ); Tenant tenant = checkTenantId(tenantId, Operation.READ);
@ -74,7 +74,7 @@ public class TenantController extends BaseController {
@RequestMapping(value = "/tenant/info/{tenantId}", method = RequestMethod.GET) @RequestMapping(value = "/tenant/info/{tenantId}", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public TenantInfo getTenantInfoById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException { public TenantInfo getTenantInfoById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
checkParameter("tenantId", strTenantId); checkParameter(TENANT_ID, strTenantId);
try { try {
TenantId tenantId = new TenantId(toUUID(strTenantId)); TenantId tenantId = new TenantId(toUUID(strTenantId));
return checkTenantInfoId(tenantId, Operation.READ); return checkTenantInfoId(tenantId, Operation.READ);

7
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java

@ -196,6 +196,13 @@ public class DefaultTbClusterService implements TbClusterService {
@Override @Override
public void pushNotificationToTransport(String serviceId, ToTransportMsg response, TbQueueCallback callback) { public void pushNotificationToTransport(String serviceId, ToTransportMsg response, TbQueueCallback callback) {
if (serviceId == null || serviceId.isEmpty()){
log.trace("pushNotificationToTransport: skipping message without serviceId [{}], (ToTransportMsg) response [{}]", serviceId, response);
if (callback != null) {
callback.onSuccess(null); //callback that message already sent, no useful payload expected
}
return;
}
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, serviceId); TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, serviceId);
log.trace("PUSHING msg: {} to:{}", response, tpi); log.trace("PUSHING msg: {} to:{}", response, tpi);
producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), response), callback); producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), response), callback);

4
application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java

@ -87,6 +87,10 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi
@Override @Override
public void sendRpcReplyToDevice(String serviceId, UUID sessionId, int requestId, String body) { public void sendRpcReplyToDevice(String serviceId, UUID sessionId, int requestId, String body) {
if (serviceId == null || serviceId.isEmpty()){
log.trace("sendRpcReplyToDevice: skipping message without serviceId [{}], sessionId[{}], requestId[{}], body[{}]", serviceId, sessionId, requestId, body);
return;
}
TransportProtos.ToServerRpcResponseMsg responseMsg = TransportProtos.ToServerRpcResponseMsg.newBuilder() TransportProtos.ToServerRpcResponseMsg responseMsg = TransportProtos.ToServerRpcResponseMsg.newBuilder()
.setRequestId(requestId) .setRequestId(requestId)
.setPayload(body).build(); .setPayload(body).build();

30
application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel
@Data
public class ActivateUserRequest {
@ApiModelProperty(position = 1, value = "The activate token to verify", example = "AAB254FF67D..")
private String activateToken;
@ApiModelProperty(position = 2, value = "The new password to set", example = "secret")
private String password;
}

31
application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel
@Data
public class ChangePasswordRequest {
@ApiModelProperty(position = 1, value = "The old password", example = "OldPassword")
private String currentPassword;
@ApiModelProperty(position = 1, value = "The new password", example = "NewPassword")
private String newPassword;
}

32
application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
@ApiModel(value = "JWT Token Pair")
@Data
@AllArgsConstructor
public class JwtTokenPair {
@ApiModelProperty(position = 1, value = "The JWT Access Token. Used to perform API calls.", example = "AAB254FF67D..")
private String token;
@ApiModelProperty(position = 1, value = "The JWT Refresh Token. Used to get new JWT Access Token if old one has expired.", example = "AAB254FF67D..")
private String refreshToken;
}

29
application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java

@ -0,0 +1,29 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel
@Data
public class ResetPasswordEmailRequest {
@ApiModelProperty(position = 1, value = "The email of the user", example = "user@example.com")
private String email;
}

30
application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.security.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel
@Data
public class ResetPasswordRequest {
@ApiModelProperty(position = 1, value = "The reset token to verify", example = "AAB254FF67D..")
private String resetToken;
@ApiModelProperty(position = 2, value = "The new password to set", example = "secret")
private String password;
}

7
application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java

@ -54,6 +54,13 @@ public class DefaultTbCoreToTransportService implements TbCoreToTransportService
@Override @Override
public void process(String nodeId, ToTransportMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure) { public void process(String nodeId, ToTransportMsg msg, Runnable onSuccess, Consumer<Throwable> onFailure) {
if (nodeId == null || nodeId.isEmpty()){
log.trace("process: skipping message without nodeId [{}], (ToTransportMsg) msg [{}]", nodeId, msg);
if (onSuccess != null) {
onSuccess.run();
}
return;
}
TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, nodeId); TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, nodeId);
UUID sessionId = new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB()); UUID sessionId = new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB());
log.trace("[{}][{}] Pushing session data to topic: {}", tpi.getFullTopicName(), sessionId, msg); log.trace("[{}][{}] Pushing session data to topic: {}", tpi.getFullTopicName(), sessionId, msg);

4
common/data/pom.xml

@ -96,6 +96,10 @@
<groupId>org.apache.commons</groupId> <groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId> <artifactId>commons-lang3</artifactId>
</dependency> </dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

18
common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java

@ -15,11 +15,15 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.id.AdminSettingsId; import org.thingsboard.server.common.data.id.AdminSettingsId;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
public class AdminSettings extends BaseData<AdminSettingsId> { public class AdminSettings extends BaseData<AdminSettingsId> {
private static final long serialVersionUID = -7670322981725511892L; private static final long serialVersionUID = -7670322981725511892L;
@ -42,6 +46,19 @@ public class AdminSettings extends BaseData<AdminSettingsId> {
this.jsonValue = adminSettings.getJsonValue(); this.jsonValue = adminSettings.getJsonValue();
} }
@ApiModelProperty(position = 1, value = "The Id of the Administration Settings, auto-generated, UUID")
@Override
public AdminSettingsId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the settings creation, in milliseconds", example = "1609459200000", readOnly = true)
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
@ApiModelProperty(position = 3, value = "The Administration Settings key, (e.g. 'general' or 'mail')")
public String getKey() { public String getKey() {
return key; return key;
} }
@ -50,6 +67,7 @@ public class AdminSettings extends BaseData<AdminSettingsId> {
this.key = key; this.key = key;
} }
@ApiModelProperty(position = 4, value = "JSON representation of the Administration Settings value")
public JsonNode getJsonValue() { public JsonNode getJsonValue() {
return jsonValue; return jsonValue;
} }

5
common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java

@ -16,6 +16,7 @@
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
public class Dashboard extends DashboardInfo { public class Dashboard extends DashboardInfo {
@ -41,6 +42,10 @@ public class Dashboard extends DashboardInfo {
this.configuration = dashboard.getConfiguration(); this.configuration = dashboard.getConfiguration();
} }
@ApiModelProperty(position = 9, value = "JSON object with main configuration of the dashboard: layouts, widgets, aliases, etc. " +
"The JSON structure of the dashboard configuration is quite complex. " +
"The easiest way to learn it is to export existing dashboard to JSON."
, dataType = "com.fasterxml.jackson.databind.JsonNode")
public JsonNode getConfiguration() { public JsonNode getConfiguration() {
return configuration; return configuration;
} }

25
common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java

@ -16,6 +16,8 @@
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -25,6 +27,7 @@ import javax.validation.Valid;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ApiModel
public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName, HasTenantId { public class DashboardInfo extends SearchTextBased<DashboardId> implements HasName, HasTenantId {
private TenantId tenantId; private TenantId tenantId;
@ -54,6 +57,22 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.mobileOrder = dashboardInfo.getMobileOrder(); this.mobileOrder = dashboardInfo.getMobileOrder();
} }
@ApiModelProperty(position = 1, value = "JSON object with the dashboard Id. " +
"Specify existing dashboard Id to update the dashboard. " +
"Referencing non-existing dashboard id will cause error. " +
"Omit this field to create new dashboard." )
@Override
public DashboardId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the dashboard creation, in milliseconds", example = "1609459200000", readOnly = true)
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Tenant Id of the dashboard can't be changed.", readOnly = true)
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }
@ -62,6 +81,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.tenantId = tenantId; this.tenantId = tenantId;
} }
@ApiModelProperty(position = 4, value = "Title of the dashboard.")
public String getTitle() { public String getTitle() {
return title; return title;
} }
@ -70,6 +90,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.title = title; this.title = title;
} }
@ApiModelProperty(position = 8, value = "Thumbnail picture for rendering of the dashboards in a grid view on mobile devices.", readOnly = true)
public String getImage() { public String getImage() {
return image; return image;
} }
@ -78,6 +99,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.image = image; this.image = image;
} }
@ApiModelProperty(position = 5, value = "List of assigned customers with their info.", readOnly = true)
public Set<ShortCustomerInfo> getAssignedCustomers() { public Set<ShortCustomerInfo> getAssignedCustomers() {
return assignedCustomers; return assignedCustomers;
} }
@ -86,6 +108,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.assignedCustomers = assignedCustomers; this.assignedCustomers = assignedCustomers;
} }
@ApiModelProperty(position = 6, value = "Hide dashboard from mobile devices. Useful if the dashboard is not designed for small screens.", readOnly = true)
public boolean isMobileHide() { public boolean isMobileHide() {
return mobileHide; return mobileHide;
} }
@ -94,6 +117,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
this.mobileHide = mobileHide; this.mobileHide = mobileHide;
} }
@ApiModelProperty(position = 7, value = "Order on mobile devices. Useful to adjust sorting of the dashboards for mobile applications", readOnly = true)
public Integer getMobileOrder() { public Integer getMobileOrder() {
return mobileOrder; return mobileOrder;
} }
@ -152,6 +176,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa
} }
} }
@ApiModelProperty(position = 4, value = "Same as title of the dashboard. Read-only field. Update the 'title' to change the 'name' of the dashboard.", readOnly = true)
@Override @Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() { public String getName() {

34
common/data/src/main/java/org/thingsboard/server/common/data/Device.java

@ -17,6 +17,9 @@ package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.data.DeviceData; import org.thingsboard.server.common.data.device.data.DeviceData;
@ -31,6 +34,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Optional; import java.util.Optional;
@ApiModel
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Slf4j @Slf4j
public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName, HasTenantId, HasCustomerId, HasOtaPackage { public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implements HasName, HasTenantId, HasCustomerId, HasOtaPackage {
@ -88,6 +92,22 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
return this; return this;
} }
@ApiModelProperty(position = 1, value = "JSON object with the device Id. " +
"Specify this field to update the device. " +
"Referencing non-existing device Id will cause error. " +
"Omit this field to create new device." )
@Override
public DeviceId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the device creation, in milliseconds", example = "1609459200000", readOnly = true)
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id. Use 'assignDeviceToTenant' to change the Tenant Id.", readOnly = true)
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }
@ -96,6 +116,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.tenantId = tenantId; this.tenantId = tenantId;
} }
@ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignDeviceToCustomer' to change the Customer Id.", readOnly = true)
public CustomerId getCustomerId() { public CustomerId getCustomerId() {
return customerId; return customerId;
} }
@ -104,6 +125,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.customerId = customerId; this.customerId = customerId;
} }
@ApiModelProperty(position = 5, required = true, value = "Unique Device Name in scope of Tenant", example = "A4B72CCDFF33")
@Override @Override
public String getName() { public String getName() {
return name; return name;
@ -113,6 +135,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.name = name; this.name = name;
} }
@ApiModelProperty(position = 6, required = true, value = "Device Profile Name", example = "Temperature Sensor")
public String getType() { public String getType() {
return type; return type;
} }
@ -121,6 +144,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.type = type; this.type = type;
} }
@ApiModelProperty(position = 7, required = true, value = "Label that may be used in widgets", example = "Room 234 Sensor")
public String getLabel() { public String getLabel() {
return label; return label;
} }
@ -129,6 +153,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.label = label; this.label = label;
} }
@ApiModelProperty(position = 8, required = true, value = "JSON object with Device Profile Id.")
public DeviceProfileId getDeviceProfileId() { public DeviceProfileId getDeviceProfileId() {
return deviceProfileId; return deviceProfileId;
} }
@ -137,6 +162,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.deviceProfileId = deviceProfileId; this.deviceProfileId = deviceProfileId;
} }
@ApiModelProperty(position = 9, value = "JSON object with content specific to type of transport in the device profile.")
public DeviceData getDeviceData() { public DeviceData getDeviceData() {
if (deviceData != null) { if (deviceData != null) {
return deviceData; return deviceData;
@ -169,6 +195,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
return getName(); return getName();
} }
@ApiModelProperty(position = 10, value = "JSON object with Ota Package Id.")
public OtaPackageId getFirmwareId() { public OtaPackageId getFirmwareId() {
return firmwareId; return firmwareId;
} }
@ -177,6 +204,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.firmwareId = firmwareId; this.firmwareId = firmwareId;
} }
@ApiModelProperty(position = 11, value = "JSON object with Ota Package Id.")
public OtaPackageId getSoftwareId() { public OtaPackageId getSoftwareId() {
return softwareId; return softwareId;
} }
@ -185,6 +213,12 @@ public class Device extends SearchTextBasedWithAdditionalInfo<DeviceId> implemen
this.softwareId = softwareId; this.softwareId = softwareId;
} }
@ApiModelProperty(position = 12, value = "Additional parameters of the device", dataType = "com.fasterxml.jackson.databind.JsonNode")
@Override
public JsonNode getAdditionalInfo() {
return super.getAdditionalInfo();
}
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();

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

@ -15,14 +15,20 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ApiModel
@Data @Data
public class DeviceInfo extends Device { public class DeviceInfo extends Device {
@ApiModelProperty(position = 13, value = "Title of the Customer that owns the device.", readOnly = true)
private String customerTitle; private String customerTitle;
@ApiModelProperty(position = 14, value = "Indicates special 'Public' Customer that is auto-generated to use the devices on public dashboards.", readOnly = true)
private boolean customerIsPublic; private boolean customerIsPublic;
@ApiModelProperty(position = 15, value = "Name of the corresponding Device Profile.", readOnly = true)
private String deviceProfileName; private String deviceProfileName;
public DeviceInfo() { public DeviceInfo() {

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

@ -15,11 +15,17 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ApiModel
@Data @Data
public class HomeDashboard extends Dashboard { public class HomeDashboard extends Dashboard {
public static final String HIDE_DASHBOARD_TOOLBAR_DESCRIPTION = "Hide dashboard toolbar flag. Useful for rendering dashboards on mobile.";
@ApiModelProperty(position = 10, value = HIDE_DASHBOARD_TOOLBAR_DESCRIPTION)
private boolean hideDashboardToolbar; private boolean hideDashboardToolbar;
public HomeDashboard(Dashboard dashboard, boolean hideDashboardToolbar) { public HomeDashboard(Dashboard dashboard, boolean hideDashboardToolbar) {

5
common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java

@ -15,13 +15,18 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
@ApiModel
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class HomeDashboardInfo { public class HomeDashboardInfo {
@ApiModelProperty(position = 1, value = "JSON object with the dashboard Id.")
private DashboardId dashboardId; private DashboardId dashboardId;
@ApiModelProperty(position = 1, value = HomeDashboard.HIDE_DASHBOARD_TOOLBAR_DESCRIPTION)
private boolean hideDashboardToolbar; private boolean hideDashboardToolbar;
} }

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

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -25,16 +27,20 @@ import org.thingsboard.server.common.data.validation.NoXss;
* Created by igor on 2/27/18. * Created by igor on 2/27/18.
*/ */
@ApiModel
@AllArgsConstructor @AllArgsConstructor
public class ShortCustomerInfo { public class ShortCustomerInfo {
@ApiModelProperty(position = 1, value = "JSON object with the customer Id.")
@Getter @Setter @Getter @Setter
private CustomerId customerId; private CustomerId customerId;
@ApiModelProperty(position = 2, value = "Title of the customer.")
@Getter @Setter @Getter @Setter
@NoXss @NoXss
private String title; private String title;
@ApiModelProperty(position = 3, value = "Indicates special 'Public' customer used to embed dashboards on public websites.")
@Getter @Setter @Getter @Setter
private boolean isPublic; private boolean isPublic;

5
common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java

@ -15,12 +15,17 @@
*/ */
package org.thingsboard.server.common.data; package org.thingsboard.server.common.data;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ApiModel
@Data @Data
public class UpdateMessage { public class UpdateMessage {
@ApiModelProperty(position = 1, value = "The message about new platform update available.")
private final String message; private final String message;
@ApiModelProperty(position = 1, value = "'True' if new platform update is available.")
private final boolean isUpdateAvailable; private final boolean isUpdateAvailable;
} }

33
common/data/src/main/java/org/thingsboard/server/common/data/User.java

@ -17,6 +17,9 @@ package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -26,6 +29,7 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.validation.NoXss; import org.thingsboard.server.common.data.validation.NoXss;
@ApiModel
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId { public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements HasName, HasTenantId, HasCustomerId {
@ -58,6 +62,23 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.lastName = user.getLastName(); this.lastName = user.getLastName();
} }
@ApiModelProperty(position = 1, value = "JSON object with the User Id. " +
"Specify this field to update the device. " +
"Referencing non-existing User Id will cause error. " +
"Omit this field to create new customer." )
@Override
public UserId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the user creation, in milliseconds", example = "1609459200000", readOnly = true)
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
@ApiModelProperty(position = 3, value = "JSON object with the Tenant Id.", readOnly = true)
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }
@ -66,6 +87,7 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.tenantId = tenantId; this.tenantId = tenantId;
} }
@ApiModelProperty(position = 4, value = "JSON object with the Customer Id.", readOnly = true)
public CustomerId getCustomerId() { public CustomerId getCustomerId() {
return customerId; return customerId;
} }
@ -74,6 +96,7 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.customerId = customerId; this.customerId = customerId;
} }
@ApiModelProperty(position = 5, required = true, value = "Email of the user", example = "user@example.com")
public String getEmail() { public String getEmail() {
return email; return email;
} }
@ -82,12 +105,14 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.email = email; this.email = email;
} }
@ApiModelProperty(position = 6, readOnly = true, value = "Duplicates the email of the user, readonly", example = "user@example.com")
@Override @Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY) @JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() { public String getName() {
return email; return email;
} }
@ApiModelProperty(position = 7, required = true, value = "Authority", example = "SYS_ADMIN, TENANT_ADMIN or CUSTOMER_USER")
public Authority getAuthority() { public Authority getAuthority() {
return authority; return authority;
} }
@ -96,6 +121,7 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.authority = authority; this.authority = authority;
} }
@ApiModelProperty(position = 8, required = true, value = "First name of the user", example = "John")
public String getFirstName() { public String getFirstName() {
return firstName; return firstName;
} }
@ -104,6 +130,7 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.firstName = firstName; this.firstName = firstName;
} }
@ApiModelProperty(position = 9, required = true, value = "Last name of the user", example = "Doe")
public String getLastName() { public String getLastName() {
return lastName; return lastName;
} }
@ -112,6 +139,12 @@ public class User extends SearchTextBasedWithAdditionalInfo<UserId> implements H
this.lastName = lastName; this.lastName = lastName;
} }
@ApiModelProperty(position = 10, value = "Additional parameters of the user", dataType = "com.fasterxml.jackson.databind.JsonNode")
@Override
public JsonNode getAdditionalInfo() {
return super.getAdditionalInfo();
}
@Override @Override
public String getSearchText() { public String getSearchText() {
return getEmail(); return getEmail();

30
common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java

@ -15,6 +15,9 @@
*/ */
package org.thingsboard.server.common.data.asset; package org.thingsboard.server.common.data.asset;
import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.HasCustomerId; import org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasName;
@ -27,6 +30,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Optional; import java.util.Optional;
@ApiModel
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId { public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements HasName, HasTenantId, HasCustomerId {
@ -67,6 +71,22 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
Optional.ofNullable(asset.getAdditionalInfo()).ifPresent(this::setAdditionalInfo); Optional.ofNullable(asset.getAdditionalInfo()).ifPresent(this::setAdditionalInfo);
} }
@ApiModelProperty(position = 1, value = "JSON object with the asset Id. " +
"Specify this field to update the asset. " +
"Referencing non-existing asset Id will cause error. " +
"Omit this field to create new asset.")
@Override
public AssetId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the asset creation, in milliseconds", example = "1609459200000", readOnly = true)
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
@ApiModelProperty(position = 3, value = "JSON object with Tenant Id.", readOnly = true)
public TenantId getTenantId() { public TenantId getTenantId() {
return tenantId; return tenantId;
} }
@ -75,6 +95,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.tenantId = tenantId; this.tenantId = tenantId;
} }
@ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignAssetToCustomer' to change the Customer Id.", readOnly = true)
public CustomerId getCustomerId() { public CustomerId getCustomerId() {
return customerId; return customerId;
} }
@ -83,6 +104,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.customerId = customerId; this.customerId = customerId;
} }
@ApiModelProperty(position = 5, required = true, value = "Unique Asset Name in scope of Tenant", example = "Empire State Building")
@Override @Override
public String getName() { public String getName() {
return name; return name;
@ -92,6 +114,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.name = name; this.name = name;
} }
@ApiModelProperty(position = 6, required = true, value = "Asset type", example = "Building")
public String getType() { public String getType() {
return type; return type;
} }
@ -100,6 +123,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
this.type = type; this.type = type;
} }
@ApiModelProperty(position = 7, required = true, value = "Label that may be used in widgets", example = "NY Building")
public String getLabel() { public String getLabel() {
return label; return label;
} }
@ -113,6 +137,12 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
return getName(); return getName();
} }
@ApiModelProperty(position = 8, value = "Additional parameters of the asset", dataType = "com.fasterxml.jackson.databind.JsonNode")
@Override
public JsonNode getAdditionalInfo() {
return super.getAdditionalInfo();
}
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();

5
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java

@ -15,13 +15,18 @@
*/ */
package org.thingsboard.server.common.data.asset; package org.thingsboard.server.common.data.asset;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetId;
@ApiModel
@Data @Data
public class AssetInfo extends Asset { public class AssetInfo extends Asset {
@ApiModelProperty(position = 9, value = "Title of the Customer that owns the asset.", readOnly = true)
private String customerTitle; private String customerTitle;
@ApiModelProperty(position = 10, value = "Indicates special 'Public' Customer that is auto-generated to use the assets on public dashboards.", readOnly = true)
private boolean customerIsPublic; private boolean customerIsPublic;
public AssetInfo() { public AssetInfo() {

4
common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.common.data.asset; package org.thingsboard.server.common.data.asset;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
@ -31,8 +32,11 @@ import java.util.List;
@Data @Data
public class AssetSearchQuery { public class AssetSearchQuery {
@ApiModelProperty(position = 3, value = "Main search parameters.")
private RelationsSearchParameters parameters; private RelationsSearchParameters parameters;
@ApiModelProperty(position = 1, value = "Type of the relation between root entity and asset (e.g. 'Contains' or 'Manages').")
private String relationType; private String relationType;
@ApiModelProperty(position = 2, value = "Array of asset types to filter the related entities (e.g. 'Building', 'Vehicle').")
private List<String> assetTypes; private List<String> assetTypes;
public EntityRelationsQuery toEntitySearchQuery() { public EntityRelationsQuery toEntitySearchQuery() {

6
common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.common.data.device; package org.thingsboard.server.common.data.device;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
@ -25,11 +27,15 @@ import org.thingsboard.server.common.data.relation.RelationsSearchParameters;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ApiModel
@Data @Data
public class DeviceSearchQuery { public class DeviceSearchQuery {
@ApiModelProperty(position = 3, value = "Main search parameters.")
private RelationsSearchParameters parameters; private RelationsSearchParameters parameters;
@ApiModelProperty(position = 1, value = "Type of the relation between root entity and device (e.g. 'Contains' or 'Manages').")
private String relationType; private String relationType;
@ApiModelProperty(position = 2, value = "Array of device types to filter the related entities (e.g. 'Temperature Sensor', 'Smoke Sensor').")
private List<String> deviceTypes; private List<String> deviceTypes;
public EntityRelationsQuery toEntitySearchQuery() { public EntityRelationsQuery toEntitySearchQuery() {

2
common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java

@ -15,9 +15,11 @@
*/ */
package org.thingsboard.server.common.data.device.data; package org.thingsboard.server.common.data.device.data;
import io.swagger.annotations.ApiModel;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceProfileType;
@ApiModel
@Data @Data
public class DefaultDeviceConfiguration implements DeviceConfiguration { public class DefaultDeviceConfiguration implements DeviceConfiguration {

2
common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java

@ -19,8 +19,10 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.annotations.ApiModel;
import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceProfileType;
@ApiModel
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo( @JsonTypeInfo(
use = JsonTypeInfo.Id.NAME, use = JsonTypeInfo.Id.NAME,

2
common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java

@ -15,8 +15,10 @@
*/ */
package org.thingsboard.server.common.data.device.data; package org.thingsboard.server.common.data.device.data;
import io.swagger.annotations.ApiModel;
import lombok.Data; import lombok.Data;
@ApiModel
@Data @Data
public class DeviceData { public class DeviceData {

2
common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java

@ -19,10 +19,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.annotations.ApiModel;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import java.io.Serializable; import java.io.Serializable;
@ApiModel
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo( @JsonTypeInfo(
use = JsonTypeInfo.Id.NAME, use = JsonTypeInfo.Id.NAME,

5
common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java

@ -18,10 +18,13 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ApiModel
public class AlarmId extends UUIDBased implements EntityId { public class AlarmId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -35,7 +38,7 @@ public class AlarmId extends UUIDBased implements EntityId {
return new AlarmId(UUID.fromString(alarmId)); return new AlarmId(UUID.fromString(alarmId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "ALARM", allowableValues = "ALARM")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.ALARM; return EntityType.ALARM;

5
common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java

@ -18,10 +18,13 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ApiModel
public class ApiUsageStateId extends UUIDBased implements EntityId { public class ApiUsageStateId extends UUIDBased implements EntityId {
@JsonCreator @JsonCreator
@ -33,7 +36,7 @@ public class ApiUsageStateId extends UUIDBased implements EntityId {
return new ApiUsageStateId(UUID.fromString(userId)); return new ApiUsageStateId(UUID.fromString(userId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "API_USAGE_STATE", allowableValues = "API_USAGE_STATE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.API_USAGE_STATE; return EntityType.API_USAGE_STATE;

5
common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java

@ -18,10 +18,13 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ApiModel
public class AssetId extends UUIDBased implements EntityId { public class AssetId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -35,7 +38,7 @@ public class AssetId extends UUIDBased implements EntityId {
return new AssetId(UUID.fromString(assetId)); return new AssetId(UUID.fromString(assetId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "ASSET", allowableValues = "ASSET")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.ASSET; return EntityType.ASSET;

5
common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java

@ -20,8 +20,11 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ApiModel
public final class CustomerId extends UUIDBased implements EntityId { public final class CustomerId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -31,7 +34,7 @@ public final class CustomerId extends UUIDBased implements EntityId {
super(id); super(id);
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "CUSTOMER", allowableValues = "CUSTOMER")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.CUSTOMER; return EntityType.CUSTOMER;

5
common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java

@ -20,8 +20,11 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ApiModel
public class DashboardId extends UUIDBased implements EntityId { public class DashboardId extends UUIDBased implements EntityId {
@JsonCreator @JsonCreator
@ -33,7 +36,7 @@ public class DashboardId extends UUIDBased implements EntityId {
return new DashboardId(UUID.fromString(dashboardId)); return new DashboardId(UUID.fromString(dashboardId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "DASHBOARD", allowableValues = "DASHBOARD")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.DASHBOARD; return EntityType.DASHBOARD;

5
common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java

@ -20,8 +20,11 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ApiModel
public class DeviceId extends UUIDBased implements EntityId { public class DeviceId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -35,8 +38,8 @@ public class DeviceId extends UUIDBased implements EntityId {
return new DeviceId(UUID.fromString(deviceId)); return new DeviceId(UUID.fromString(deviceId));
} }
@JsonIgnore
@Override @Override
@ApiModelProperty(position = 2, required = true, value = "string", example = "DEVICE", allowableValues = "DEVICE")
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.DEVICE; return EntityType.DEVICE;
} }

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

@ -20,6 +20,7 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
public class DeviceProfileId extends UUIDBased implements EntityId { public class DeviceProfileId extends UUIDBased implements EntityId {
@ -35,7 +36,7 @@ public class DeviceProfileId extends UUIDBased implements EntityId {
return new DeviceProfileId(UUID.fromString(deviceProfileId)); return new DeviceProfileId(UUID.fromString(deviceProfileId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "DEVICE_PROFILE", allowableValues = "DEVICE_PROFILE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.DEVICE_PROFILE; return EntityType.DEVICE_PROFILE;

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

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ -35,7 +36,7 @@ public class EdgeId extends UUIDBased implements EntityId {
return new EdgeId(UUID.fromString(integrationId)); return new EdgeId(UUID.fromString(integrationId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "EDGE", allowableValues = "EDGE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.EDGE; return EntityType.EDGE;

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

@ -17,6 +17,7 @@ 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.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ -37,6 +38,7 @@ public class EntityViewId extends UUIDBased implements EntityId {
return new EntityViewId(UUID.fromString(entityViewID)); return new EntityViewId(UUID.fromString(entityViewID));
} }
@ApiModelProperty(position = 2, required = true, value = "string", example = "ENTITY_VIEW", allowableValues = "ENTITY_VIEW")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.ENTITY_VIEW; return EntityType.ENTITY_VIEW;

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

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ -35,7 +36,7 @@ public class OtaPackageId extends UUIDBased implements EntityId {
return new OtaPackageId(UUID.fromString(firmwareId)); return new OtaPackageId(UUID.fromString(firmwareId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "OTA_PACKAGE", allowableValues = "OTA_PACKAGE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.OTA_PACKAGE; return EntityType.OTA_PACKAGE;

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

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ -31,7 +32,7 @@ public final class RpcId extends UUIDBased implements EntityId {
super(id); super(id);
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "RPC", allowableValues = "RPC")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.RPC; return EntityType.RPC;

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

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ -29,7 +30,7 @@ public class RuleChainId extends UUIDBased implements EntityId {
super(id); super(id);
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "RULE_CHAIN", allowableValues = "RULE_CHAIN")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.RULE_CHAIN; return EntityType.RULE_CHAIN;

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

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ -29,7 +30,7 @@ public class RuleNodeId extends UUIDBased implements EntityId {
super(id); super(id);
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "RULE_NODE", allowableValues = "RULE_NODE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.RULE_NODE; return EntityType.RULE_NODE;

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

@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import java.util.UUID; import java.util.UUID;
@ -31,7 +32,7 @@ public class TbResourceId extends UUIDBased implements EntityId {
super(id); super(id);
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "TB_RESOURCE", allowableValues = "TB_RESOURCE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.TB_RESOURCE; return EntityType.TB_RESOURCE;

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

@ -20,6 +20,7 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
public final class TenantId extends UUIDBased implements EntityId { public final class TenantId extends UUIDBased implements EntityId {
@ -34,7 +35,7 @@ public final class TenantId extends UUIDBased implements EntityId {
super(id); super(id);
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "TENANT", allowableValues = "TENANT")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.TENANT; return EntityType.TENANT;

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

@ -20,6 +20,7 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
public class TenantProfileId extends UUIDBased implements EntityId { public class TenantProfileId extends UUIDBased implements EntityId {
@ -35,7 +36,7 @@ public class TenantProfileId extends UUIDBased implements EntityId {
return new TenantProfileId(UUID.fromString(tenantProfileId)); return new TenantProfileId(UUID.fromString(tenantProfileId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "TENANT_PROFILE", allowableValues = "TENANT_PROFILE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.TENANT_PROFILE; return EntityType.TENANT_PROFILE;

5
common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java

@ -15,9 +15,13 @@
*/ */
package org.thingsboard.server.common.data.id; package org.thingsboard.server.common.data.id;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID; import java.util.UUID;
@ApiModel
public abstract class UUIDBased implements HasUUID, Serializable { public abstract class UUIDBased implements HasUUID, Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ -33,6 +37,7 @@ public abstract class UUIDBased implements HasUUID, Serializable {
this.id = id; this.id = id;
} }
@ApiModelProperty(position = 1, required = true, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9")
public UUID getId() { public UUID getId() {
return id; return id;
} }

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

@ -20,6 +20,7 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
public class UserId extends UUIDBased implements EntityId { public class UserId extends UUIDBased implements EntityId {
@ -33,7 +34,7 @@ public class UserId extends UUIDBased implements EntityId {
return new UserId(UUID.fromString(userId)); return new UserId(UUID.fromString(userId));
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "USER", allowableValues = "USER")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.USER; return EntityType.USER;

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

@ -20,6 +20,7 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
public final class WidgetTypeId extends UUIDBased implements EntityId { public final class WidgetTypeId extends UUIDBased implements EntityId {
@ -31,7 +32,7 @@ public final class WidgetTypeId extends UUIDBased implements EntityId {
super(id); super(id);
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "WIDGET_TYPE", allowableValues = "WIDGET_TYPE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.WIDGET_TYPE; return EntityType.WIDGET_TYPE;

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

@ -20,6 +20,7 @@ import java.util.UUID;
import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
public final class WidgetsBundleId extends UUIDBased implements EntityId { public final class WidgetsBundleId extends UUIDBased implements EntityId {
@ -31,7 +32,7 @@ public final class WidgetsBundleId extends UUIDBased implements EntityId {
super(id); super(id);
} }
@JsonIgnore @ApiModelProperty(position = 2, required = true, value = "string", example = "WIDGETS_BUNDLE", allowableValues = "WIDGETS_BUNDLE")
@Override @Override
public EntityType getEntityType() { public EntityType getEntityType() {
return EntityType.WIDGETS_BUNDLE; return EntityType.WIDGETS_BUNDLE;

7
common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java

@ -17,12 +17,15 @@ package org.thingsboard.server.common.data.page;
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.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ApiModel
public class PageData<T> { public class PageData<T> {
private final List<T> data; private final List<T> data;
@ -45,18 +48,22 @@ public class PageData<T> {
this.hasNext = hasNext; this.hasNext = hasNext;
} }
@ApiModelProperty(position = 1, value = "Array of the entities", readOnly = true)
public List<T> getData() { public List<T> getData() {
return data; return data;
} }
@ApiModelProperty(position = 2, value = "Total number of available pages. Calculated based on the 'pageSize' request parameter and total number of entities that match search criteria", readOnly = true)
public int getTotalPages() { public int getTotalPages() {
return totalPages; return totalPages;
} }
@ApiModelProperty(position = 3, value = "Total number of elements in all available pages", readOnly = true)
public long getTotalElements() { public long getTotalElements() {
return totalElements; return totalElements;
} }
@ApiModelProperty(position = 4, value = "'false' value indicates the end of the result set", readOnly = true)
@JsonProperty("hasNext") @JsonProperty("hasNext")
public boolean hasNext() { public boolean hasNext() {
return hasNext; return hasNext;

26
common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java

@ -16,6 +16,8 @@
package org.thingsboard.server.common.data.plugin; package org.thingsboard.server.common.data.plugin;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.*; import lombok.*;
import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.ComponentDescriptorId; import org.thingsboard.server.common.data.id.ComponentDescriptorId;
@ -23,16 +25,23 @@ import org.thingsboard.server.common.data.id.ComponentDescriptorId;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
*/ */
@ApiModel
@ToString @ToString
public class ComponentDescriptor extends SearchTextBased<ComponentDescriptorId> { public class ComponentDescriptor extends SearchTextBased<ComponentDescriptorId> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@ApiModelProperty(position = 3, value = "Type of the Rule Node", readOnly = true)
@Getter @Setter private ComponentType type; @Getter @Setter private ComponentType type;
@ApiModelProperty(position = 4, value = "Scope of the Rule Node. Always set to 'TENANT', since no rule chains on the 'SYSTEM' level yet.", readOnly = true, allowableValues = "TENANT", example = "TENANT")
@Getter @Setter private ComponentScope scope; @Getter @Setter private ComponentScope scope;
@ApiModelProperty(position = 5, value = "Name of the Rule Node. Taken from the @RuleNode annotation.", readOnly = true, example = "Custom Rule Node")
@Getter @Setter private String name; @Getter @Setter private String name;
@ApiModelProperty(position = 6, value = "Full name of the Java class that implements the Rule Engine Node interface.", readOnly = true, example = "com.mycompany.CustomRuleNode")
@Getter @Setter private String clazz; @Getter @Setter private String clazz;
@ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", readOnly = true)
@Getter @Setter private transient JsonNode configurationDescriptor; @Getter @Setter private transient JsonNode configurationDescriptor;
@ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", readOnly = true)
@Getter @Setter private String actions; @Getter @Setter private String actions;
public ComponentDescriptor() { public ComponentDescriptor() {
@ -53,12 +62,26 @@ public class ComponentDescriptor extends SearchTextBased<ComponentDescriptorId>
this.actions = plugin.getActions(); this.actions = plugin.getActions();
} }
@ApiModelProperty(position = 1, value = "JSON object with the descriptor Id. " +
"Specify existing descriptor id to update the descriptor. " +
"Referencing non-existing descriptor Id will cause error. " +
"Omit this field to create new descriptor." )
@Override
public ComponentDescriptorId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the descriptor creation, in milliseconds", example = "1609459200000", readOnly = true)
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
@Override @Override
public String getSearchText() { public String getSearchText() {
return name; return name;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
@ -84,4 +107,5 @@ public class ComponentDescriptor extends SearchTextBased<ComponentDescriptorId>
result = 31 * result + (actions != null ? actions.hashCode() : 0); result = 31 * result + (actions != null ? actions.hashCode() : 0);
return result; return result;
} }
} }

9
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java

@ -15,6 +15,8 @@
*/ */
package org.thingsboard.server.common.data.relation; package org.thingsboard.server.common.data.relation;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -26,15 +28,22 @@ import java.util.UUID;
/** /**
* Created by ashvayka on 03.05.17. * Created by ashvayka on 03.05.17.
*/ */
@ApiModel
@Data @Data
@AllArgsConstructor @AllArgsConstructor
public class RelationsSearchParameters { public class RelationsSearchParameters {
@ApiModelProperty(position = 1, value = "Root entity id to start search from.")
private UUID rootId; private UUID rootId;
@ApiModelProperty(position = 2, value = "Type of the root entity.")
private EntityType rootType; private EntityType rootType;
@ApiModelProperty(position = 3, value = "Type of the root entity.")
private EntitySearchDirection direction; private EntitySearchDirection direction;
@ApiModelProperty(position = 4, value = "Type of the relation.")
private RelationTypeGroup relationTypeGroup; private RelationTypeGroup relationTypeGroup;
@ApiModelProperty(position = 5, value = "Maximum level of the search depth.")
private int maxLevel = 1; private int maxLevel = 1;
@ApiModelProperty(position = 6, value = "Fetch entities that match the last level of search. Useful to find Devices that are strictly 'maxLevel' relations away from the root entity.")
private boolean fetchLastLevelOnly; private boolean fetchLastLevelOnly;
public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel, boolean fetchLastLevelOnly) { public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel, boolean fetchLastLevelOnly) {

28
common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java

@ -15,16 +15,18 @@
*/ */
package org.thingsboard.server.common.data.security; package org.thingsboard.server.common.data.security;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceCredentialsId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
@ApiModel
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public class DeviceCredentials extends BaseData<DeviceCredentialsId> implements DeviceCredentialsFilter { public class DeviceCredentials extends BaseData<DeviceCredentialsId> implements DeviceCredentialsFilter {
private static final long serialVersionUID = -7869261127032877765L; private static final long serialVersionUID = -7869261127032877765L;
private DeviceId deviceId; private DeviceId deviceId;
private DeviceCredentialsType credentialsType; private DeviceCredentialsType credentialsType;
private String credentialsId; private String credentialsId;
@ -46,6 +48,21 @@ public class DeviceCredentials extends BaseData<DeviceCredentialsId> implements
this.credentialsValue = deviceCredentials.getCredentialsValue(); this.credentialsValue = deviceCredentials.getCredentialsValue();
} }
@ApiModelProperty(position = 1, required = true, readOnly = true, value = "The Id is automatically generated during device creation. " +
"Use 'getDeviceCredentialsByDeviceId' to obtain the id based on device id. " +
"Use 'updateDeviceCredentials' to update device credentials. ", example = "784f394c-42b6-435a-983c-b7beff2784f9")
@Override
public DeviceCredentialsId getId() {
return super.getId();
}
@ApiModelProperty(position = 2, value = "Timestamp of the device credentials creation, in milliseconds", example = "1609459200000")
@Override
public long getCreatedTime() {
return super.getCreatedTime();
}
@ApiModelProperty(position = 3, required = true, value = "JSON object with the device Id.")
public DeviceId getDeviceId() { public DeviceId getDeviceId() {
return deviceId; return deviceId;
} }
@ -54,6 +71,7 @@ public class DeviceCredentials extends BaseData<DeviceCredentialsId> implements
this.deviceId = deviceId; this.deviceId = deviceId;
} }
@ApiModelProperty(position = 4, value = "Type of the credentials", allowableValues="ACCESS_TOKEN, X509_CERTIFICATE, MQTT_BASIC, LWM2M_CREDENTIALS")
@Override @Override
public DeviceCredentialsType getCredentialsType() { public DeviceCredentialsType getCredentialsType() {
return credentialsType; return credentialsType;
@ -63,6 +81,11 @@ public class DeviceCredentials extends BaseData<DeviceCredentialsId> implements
this.credentialsType = credentialsType; this.credentialsType = credentialsType;
} }
@ApiModelProperty(position = 5, required = true, value = "Unique Credentials Id per platform instance. " +
"Used to lookup credentials from the database. " +
"By default, new access token for your device. " +
"Depends on the type of the credentials."
, example = "Access token or other value that depends on the credentials type")
@Override @Override
public String getCredentialsId() { public String getCredentialsId() {
return credentialsId; return credentialsId;
@ -72,6 +95,9 @@ public class DeviceCredentials extends BaseData<DeviceCredentialsId> implements
this.credentialsId = credentialsId; this.credentialsId = credentialsId;
} }
@ApiModelProperty(position = 6, value = "Value of the credentials. " +
"Null in case of ACCESS_TOKEN credentials type. Base64 value in case of X509_CERTIFICATE. " +
"Complex object in case of MQTT_BASIC and LWM2M_CREDENTIALS", example = "Null in case of ACCESS_TOKEN. See model definition.")
public String getCredentialsValue() { public String getCredentialsValue() {
return credentialsValue; return credentialsValue;
} }

7
common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java

@ -15,15 +15,20 @@
*/ */
package org.thingsboard.server.common.data.security.model; package org.thingsboard.server.common.data.security.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
@ApiModel
@Data @Data
public class SecuritySettings implements Serializable { public class SecuritySettings implements Serializable {
@ApiModelProperty(position = 1, value = "The user password policy object." )
private UserPasswordPolicy passwordPolicy; private UserPasswordPolicy passwordPolicy;
@ApiModelProperty(position = 2, value = "Maximum number of failed login attempts allowed before user account is locked." )
private Integer maxFailedLoginAttempts; private Integer maxFailedLoginAttempts;
@ApiModelProperty(position = 3, value = "Email to use for notifications about locked users." )
private String userLockoutNotificationEmail; private String userLockoutNotificationEmail;
} }

10
common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java

@ -15,20 +15,30 @@
*/ */
package org.thingsboard.server.common.data.security.model; package org.thingsboard.server.common.data.security.model;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
@ApiModel
@Data @Data
public class UserPasswordPolicy implements Serializable { public class UserPasswordPolicy implements Serializable {
@ApiModelProperty(position = 1, value = "Minimum number of symbols in the password." )
private Integer minimumLength; private Integer minimumLength;
@ApiModelProperty(position = 1, value = "Minimum number of uppercase letters in the password." )
private Integer minimumUppercaseLetters; private Integer minimumUppercaseLetters;
@ApiModelProperty(position = 1, value = "Minimum number of lowercase letters in the password." )
private Integer minimumLowercaseLetters; private Integer minimumLowercaseLetters;
@ApiModelProperty(position = 1, value = "Minimum number of digits in the password." )
private Integer minimumDigits; private Integer minimumDigits;
@ApiModelProperty(position = 1, value = "Minimum number of special in the password." )
private Integer minimumSpecialCharacters; private Integer minimumSpecialCharacters;
@ApiModelProperty(position = 1, value = "Password expiration period (days). Force expiration of the password." )
private Integer passwordExpirationPeriodDays; private Integer passwordExpirationPeriodDays;
@ApiModelProperty(position = 1, value = "Password reuse frequency (days). Disallow to use the same password for the defined number of days" )
private Integer passwordReuseFrequencyDays; private Integer passwordReuseFrequencyDays;
} }

6
common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java

@ -15,13 +15,19 @@
*/ */
package org.thingsboard.server.common.data.sms.config; package org.thingsboard.server.common.data.sms.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ApiModel
@Data @Data
public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration { public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration {
@ApiModelProperty(position = 1, value = "The AWS SNS Access Key ID.")
private String accessKeyId; private String accessKeyId;
@ApiModelProperty(position = 2, value = "The AWS SNS Access Key.")
private String secretAccessKey; private String secretAccessKey;
@ApiModelProperty(position = 3, value = "The AWS region.")
private String region; private String region;
@Override @Override

6
common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java

@ -15,13 +15,19 @@
*/ */
package org.thingsboard.server.common.data.sms.config; package org.thingsboard.server.common.data.sms.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ApiModel
@Data @Data
public class TestSmsRequest { public class TestSmsRequest {
@ApiModelProperty(position = 1, value = "The SMS provider configuration")
private SmsProviderConfiguration providerConfiguration; private SmsProviderConfiguration providerConfiguration;
@ApiModelProperty(position = 2, value = "The phone number or other identifier to specify as a recipient of the SMS.")
private String numberTo; private String numberTo;
@ApiModelProperty(position = 3, value = "The test message")
private String message; private String message;
} }

6
common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java

@ -15,13 +15,19 @@
*/ */
package org.thingsboard.server.common.data.sms.config; package org.thingsboard.server.common.data.sms.config;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ApiModel
@Data @Data
public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration { public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration {
@ApiModelProperty(position = 1, value = "Twilio account Sid.")
private String accountSid; private String accountSid;
@ApiModelProperty(position = 2, value = "Twilio account Token.")
private String accountToken; private String accountToken;
@ApiModelProperty(position = 3, value = "The number/id of a sender.")
private String numberFrom; private String numberFrom;
@Override @Override

28
common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java

@ -0,0 +1,28 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.id;
import org.junit.Assert;
import org.junit.Test;
public class EntityIdTest {
@Test
public void givenConstantNullUuid_whenCompare_thenToStringEqualsPredefinedUuid() {
Assert.assertEquals("13814000-1dd2-11b2-8080-808080808080", EntityId.NULL_UUID.toString());
}
}

5
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -136,8 +136,9 @@ public final class TbMsg implements Serializable {
tbMsg.data, ruleChainId, null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback()); tbMsg.data, ruleChainId, null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback());
} }
public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { //used for enqueueForTellNext
return new TbMsg(tbMsg.getQueueName(), UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(), public static TbMsg newMsg(TbMsg tbMsg, String queueName, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(queueName, UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(),
tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ruleNodeExecCounter.get(), TbMsgCallback.EMPTY); tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, tbMsg.ruleNodeExecCounter.get(), TbMsgCallback.EMPTY);
} }

22
dao/pom.xml

@ -201,6 +201,11 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId> <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId> <artifactId>spring-test</artifactId>
@ -211,6 +216,16 @@
<artifactId>hsqldb</artifactId> <artifactId>hsqldb</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>jdbc</artifactId>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId> <artifactId>spring-context-support</artifactId>
@ -239,7 +254,14 @@
<artifactId>maven-surefire-plugin</artifactId> <artifactId>maven-surefire-plugin</artifactId>
<version>${surfire.version}</version> <version>${surfire.version}</version>
<configuration> <configuration>
<excludes>
<exclude>**/sql/*Test.java</exclude>
<exclude>**/sql/*/*Test.java</exclude>
<exclude>**/psql/*Test.java</exclude>
<exclude>**/nosql/*Test.java</exclude>
</excludes>
<includes> <includes>
<include>**/*Test.java</include>
<include>**/*TestSuite.java</include> <include>**/*TestSuite.java</include>
</includes> </includes>
</configuration> </configuration>

39
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java

@ -223,6 +223,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, aus.entity_id, " + private static final String SELECT_API_USAGE_STATE = "(select aus.id, aus.created_time, aus.tenant_id, aus.entity_id, " +
"coalesce((select title from tenant where id = aus.entity_id), (select title from customer where id = aus.entity_id)) as name " + "coalesce((select title from tenant where id = aus.entity_id), (select title from customer where id = aus.entity_id)) as name " +
"from api_usage_state as aus)"; "from api_usage_state as aus)";
static final int MAX_LEVEL_DEFAULT = 50; //This value has to be reasonable small to prevent infinite recursion as early as possible
static { static {
entityTableMap.put(EntityType.ASSET, "asset"); entityTableMap.put(EntityType.ASSET, "asset");
@ -239,18 +240,30 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{
EntityType.TENANT, EntityType.CUSTOMER, EntityType.USER, EntityType.DASHBOARD, EntityType.ASSET, EntityType.DEVICE, EntityType.ENTITY_VIEW}; EntityType.TENANT, EntityType.CUSTOMER, EntityType.USER, EntityType.DASHBOARD, EntityType.ASSET, EntityType.DEVICE, EntityType.ENTITY_VIEW};
private static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, relation_type, lvl) AS (" + private static final String HIERARCHICAL_QUERY_TEMPLATE = " FROM (WITH RECURSIVE related_entities(from_id, from_type, to_id, to_type, lvl, path) AS (" +
" SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" + " SELECT from_id, from_type, to_id, to_type," +
" FROM relation" + " 1 as lvl," +
" ARRAY[$in_id] as path" + // initial path
" FROM relation " +
" WHERE $in_id = :relation_root_id and $in_type = :relation_root_type and relation_type_group = 'COMMON'" + " WHERE $in_id = :relation_root_id and $in_type = :relation_root_type and relation_type_group = 'COMMON'" +
" GROUP BY from_id, from_type, to_id, to_type, lvl, path" +
" UNION ALL" + " UNION ALL" +
" SELECT r.from_id, r.from_type, r.to_id, r.to_type, r.relation_type, lvl + 1" + " SELECT r.from_id, r.from_type, r.to_id, r.to_type," +
" (re.lvl + 1) as lvl, " +
" (re.path || ARRAY[r.$in_id]) as path" +
" FROM relation r" + " FROM relation r" +
" INNER JOIN related_entities re ON" + " INNER JOIN related_entities re ON" +
" r.$in_id = re.$out_id and r.$in_type = re.$out_type and" + " r.$in_id = re.$out_id and r.$in_type = re.$out_type and" +
" relation_type_group = 'COMMON' %s)" + " relation_type_group = 'COMMON' " +
" SELECT re.$out_id entity_id, re.$out_type entity_type, max(re.lvl) lvl" + " AND r.$in_id NOT IN (SELECT * FROM unnest(re.path)) " +
" from related_entities re" + " %s" +
" GROUP BY r.from_id, r.from_type, r.to_id, r.to_type, (re.lvl + 1), (re.path || ARRAY[r.$in_id])" +
" )" +
" SELECT re.$out_id entity_id, re.$out_type entity_type, max(r_int.lvl) lvl" +
" from related_entities r_int" +
" INNER JOIN relation re ON re.from_id = r_int.from_id AND re.from_type = r_int.from_type" +
" AND re.to_id = r_int.to_id AND re.to_type = r_int.to_type" +
" AND re.relation_type_group = 'COMMON'" +
" %s GROUP BY entity_id, entity_type) entity"; " %s GROUP BY entity_id, entity_type) entity";
private static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from"); private static final String HIERARCHICAL_TO_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "to").replace("$out", "from");
private static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to"); private static final String HIERARCHICAL_FROM_QUERY_TEMPLATE = HIERARCHICAL_QUERY_TEMPLATE.replace("$in", "from").replace("$out", "to");
@ -580,7 +593,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
.append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type"); .append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type");
notExistsPart.append(")"); notExistsPart.append(")");
whereFilter += " and ( re.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")"; whereFilter += " and ( r_int.lvl = " + entityFilter.getMaxLevel() + " OR " + notExistsPart.toString() + ")";
} }
from = String.format(from, lvlFilter, whereFilter); from = String.format(from, lvlFilter, whereFilter);
String query = "( " + selectFields + from + ")"; String query = "( " + selectFields + from + ")";
@ -659,7 +672,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
.append(whereFilter.toString().replaceAll("re\\.", "nr\\.")); .append(whereFilter.toString().replaceAll("re\\.", "nr\\."));
notExistsPart.append(")"); notExistsPart.append(")");
whereFilter.append(" and ( re.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")"); whereFilter.append(" and ( r_int.lvl = ").append(entityFilter.getMaxLevel()).append(" OR ").append(notExistsPart.toString()).append(")");
} }
from = String.format(from, lvlFilter, " WHERE " + whereFilter); from = String.format(from, lvlFilter, " WHERE " + whereFilter);
return "( " + selectFields + from + ")"; return "( " + selectFields + from + ")";
@ -693,8 +706,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
return whereFilter.toString(); return whereFilter.toString();
} }
private String getLvlFilter(int maxLevel) { String getLvlFilter(int maxLevel) {
return maxLevel > 0 ? ("and lvl <= " + (maxLevel - 1)) : ""; return "and re.lvl <= " + (getMaxLevel(maxLevel) - 1);
}
int getMaxLevel(int maxLevel) {
return maxLevel > 0 ? maxLevel : MAX_LEVEL_DEFAULT;
} }
private String getQueryTemplate(EntitySearchDirection direction) { private String getQueryTemplate(EntitySearchDirection direction) {

30
dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters;
import org.junit.runner.RunWith;
@RunWith(ClasspathSuite.class)
@ClassnameFilters({
"org.thingsboard.server.dao.service.psql.*SqlTest",
"org.thingsboard.server.dao.service.attributes.psql.*SqlTest",
"org.thingsboard.server.dao.service.event.psql.*SqlTest",
"org.thingsboard.server.dao.service.timeseries.psql.*SqlTest"
})
public class PostgreSqlDaoServiceTestSuite {
}

64
dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java

@ -0,0 +1,64 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao;
import com.google.common.base.Charsets;
import com.google.common.io.Resources;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.net.URL;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
@Slf4j
public class PostgreSqlInitializer {
private static final List<String> sqlFiles = List.of(
"sql/schema-ts-psql.sql",
"sql/schema-entities.sql",
"sql/schema-entities-idx.sql",
"sql/system-data.sql",
"sql/system-test-psql.sql");
private static final String dropAllTablesSqlFile = "sql/psql/drop-all-tables.sql";
public static void initDb(Connection conn) {
cleanUpDb(conn);
log.info("initialize Postgres DB...");
try {
for (String sqlFile : sqlFiles) {
URL sqlFileUrl = Resources.getResource(sqlFile);
String sql = Resources.toString(sqlFileUrl, Charsets.UTF_8);
conn.createStatement().execute(sql);
}
} catch (IOException | SQLException e) {
throw new RuntimeException("Unable to init the Postgres database. Reason: " + e.getMessage(), e);
}
log.info("Postgres DB is initialized!");
}
private static void cleanUpDb(Connection conn) {
log.info("clean up Postgres DB...");
try {
URL dropAllTableSqlFileUrl = Resources.getResource(dropAllTablesSqlFile);
String dropAllTablesSql = Resources.toString(dropAllTableSqlFileUrl, Charsets.UTF_8);
conn.createStatement().execute(dropAllTablesSql);
} catch (IOException | SQLException e) {
throw new RuntimeException("Unable to clean up the Postgres database. Reason: " + e.getMessage(), e);
}
}
}

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java

@ -47,7 +47,7 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
public class BaseDeviceProfileServiceTest extends AbstractServiceTest { public abstract class BaseDeviceProfileServiceTest extends AbstractServiceTest {
private IdComparator<DeviceProfile> idComparator = new IdComparator<>(); private IdComparator<DeviceProfile> idComparator = new IdComparator<>();
private IdComparator<DeviceProfileInfo> deviceProfileInfoIdComparator = new IdComparator<>(); private IdComparator<DeviceProfileInfo> deviceProfileInfoIdComparator = new IdComparator<>();

200
dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java

@ -17,14 +17,17 @@ package org.thingsboard.server.dao.service;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.RandomUtils;
import org.hamcrest.Matchers;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
@ -69,6 +72,7 @@ import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity;
import org.thingsboard.server.dao.sql.relation.RelationRepository;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.util.ArrayList; import java.util.ArrayList;
@ -82,9 +86,13 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.hamcrest.MatcherAssert.assertThat;
@Slf4j
public abstract class BaseEntityServiceTest extends AbstractServiceTest { public abstract class BaseEntityServiceTest extends AbstractServiceTest {
static final int ENTITY_COUNT = 5;
@Autowired @Autowired
private AttributesService attributesService; private AttributesService attributesService;
@ -96,6 +104,9 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
@Autowired @Autowired
private JdbcTemplate template; private JdbcTemplate template;
@Autowired
private RelationRepository relationRepository;
@Before @Before
public void before() { public void before() {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
@ -110,7 +121,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
tenantService.deleteTenant(tenantId); tenantService.deleteTenant(tenantId);
} }
@Test @Test
public void testCountEntitiesByQuery() throws InterruptedException { public void testCountEntitiesByQuery() throws InterruptedException {
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
@ -154,12 +165,12 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
Assert.assertEquals(0, count); Assert.assertEquals(0, count);
} }
@Test @Test
public void testCountHierarchicalEntitiesByQuery() throws InterruptedException { public void testCountHierarchicalEntitiesByQuery() throws InterruptedException {
List<Asset> assets = new ArrayList<>(); List<Asset> assets = new ArrayList<>();
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>()); createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
RelationsQueryFilter filter = new RelationsQueryFilter(); RelationsQueryFilter filter = new RelationsQueryFilter();
filter.setRootEntity(tenantId); filter.setRootEntity(tenantId);
@ -168,7 +179,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
EntityCountQuery countQuery = new EntityCountQuery(filter); EntityCountQuery countQuery = new EntityCountQuery(filter);
long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); long count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
Assert.assertEquals(30, count); Assert.assertEquals(31, count); //due to the loop relations in hierarchy, the TenantId included in total count (1*Tenant + 5*Asset + 5*5*Devices = 31)
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE)))); filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery); count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
@ -304,11 +315,25 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
@Test @Test
public void testHierarchicalFindEntityDataWithAttributesByQuery() throws ExecutionException, InterruptedException { public void testHierarchicalFindEntityDataWithAttributesByQuery() throws ExecutionException, InterruptedException {
doTestHierarchicalFindEntityDataWithAttributesByQuery(0, false);
}
@Test
public void testHierarchicalFindEntityDataWithAttributesByQueryWithLevel() throws ExecutionException, InterruptedException {
doTestHierarchicalFindEntityDataWithAttributesByQuery(2, false);
}
@Test
public void testHierarchicalFindEntityDataWithAttributesByQueryWithLastLevelOnly() throws ExecutionException, InterruptedException {
doTestHierarchicalFindEntityDataWithAttributesByQuery(2, true);
}
private void doTestHierarchicalFindEntityDataWithAttributesByQuery(final int maxLevel, final boolean fetchLastLevelOnly) throws ExecutionException, InterruptedException {
List<Asset> assets = new ArrayList<>(); List<Asset> assets = new ArrayList<>();
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
List<Long> temperatures = new ArrayList<>(); List<Long> temperatures = new ArrayList<>();
List<Long> highTemperatures = new ArrayList<>(); List<Long> highTemperatures = new ArrayList<>();
createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures); createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>(); List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) { for (int i = 0; i < devices.size(); i++) {
@ -321,6 +346,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
filter.setRootEntity(tenantId); filter.setRootEntity(tenantId);
filter.setDirection(EntitySearchDirection.FROM); filter.setDirection(EntitySearchDirection.FROM);
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE)))); filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
filter.setMaxLevel(maxLevel);
filter.setFetchLastLevelOnly(fetchLastLevelOnly);
EntityDataSortOrder sortOrder = new EntityDataSortOrder( EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
@ -373,14 +400,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId);
} }
@Test @Test
public void testHierarchicalFindDevicesWithAttributesByQuery() throws ExecutionException, InterruptedException { public void testHierarchicalFindDevicesWithAttributesByQuery() throws ExecutionException, InterruptedException {
List<Asset> assets = new ArrayList<>(); List<Asset> assets = new ArrayList<>();
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
List<Long> temperatures = new ArrayList<>(); List<Long> temperatures = new ArrayList<>();
List<Long> highTemperatures = new ArrayList<>(); List<Long> highTemperatures = new ArrayList<>();
createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures); createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>(); List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) { for (int i = 0; i < devices.size(); i++) {
@ -393,6 +419,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
filter.setRootEntity(tenantId); filter.setRootEntity(tenantId);
filter.setDirection(EntitySearchDirection.FROM); filter.setDirection(EntitySearchDirection.FROM);
filter.setRelationType("Contains"); filter.setRelationType("Contains");
filter.setMaxLevel(2);
filter.setFetchLastLevelOnly(true);
EntityDataSortOrder sortOrder = new EntityDataSortOrder( EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
@ -446,14 +474,14 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId);
} }
@Test @Test
public void testHierarchicalFindAssetsWithAttributesByQuery() throws ExecutionException, InterruptedException { public void testHierarchicalFindAssetsWithAttributesByQuery() throws ExecutionException, InterruptedException {
List<Asset> assets = new ArrayList<>(); List<Asset> assets = new ArrayList<>();
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
List<Long> consumptions = new ArrayList<>(); List<Long> consumptions = new ArrayList<>();
List<Long> highConsumptions = new ArrayList<>(); List<Long> highConsumptions = new ArrayList<>();
createTestHierarchy(assets, devices, consumptions, highConsumptions, new ArrayList<>(), new ArrayList<>()); createTestHierarchy(tenantId, assets, devices, consumptions, highConsumptions, new ArrayList<>(), new ArrayList<>());
List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>(); List<ListenableFuture<List<Void>>> attributeFutures = new ArrayList<>();
for (int i = 0; i < assets.size(); i++) { for (int i = 0; i < assets.size(); i++) {
@ -518,8 +546,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId);
} }
private void createTestHierarchy(List<Asset> assets, List<Device> devices, List<Long> consumptions, List<Long> highConsumptions, List<Long> temperatures, List<Long> highTemperatures) throws InterruptedException { private void createTestHierarchy(TenantId tenantId, List<Asset> assets, List<Device> devices, List<Long> consumptions, List<Long> highConsumptions, List<Long> temperatures, List<Long> highTemperatures) throws InterruptedException {
for (int i = 0; i < 5; i++) { for (int i = 0; i < ENTITY_COUNT; i++) {
Asset asset = new Asset(); Asset asset = new Asset();
asset.setTenantId(tenantId); asset.setTenantId(tenantId);
asset.setName("Asset" + i); asset.setName("Asset" + i);
@ -529,18 +557,19 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
//TO make sure devices have different created time //TO make sure devices have different created time
Thread.sleep(1); Thread.sleep(1);
assets.add(asset); assets.add(asset);
EntityRelation er = new EntityRelation(); createRelation(tenantId, "Manages", tenantId, asset.getId());
er.setFrom(tenantId);
er.setTo(asset.getId());
er.setType("Manages");
er.setTypeGroup(RelationTypeGroup.COMMON);
relationService.saveRelation(tenantId, er);
long consumption = (long) (Math.random() * 100); long consumption = (long) (Math.random() * 100);
consumptions.add(consumption); consumptions.add(consumption);
if (consumption > 50) { if (consumption > 50) {
highConsumptions.add(consumption); highConsumptions.add(consumption);
} }
for (int j = 0; j < 5; j++) {
//tenant -> asset : one-to-one but many edges
for (int n = 0; n < ENTITY_COUNT; n++) {
createRelation(tenantId, "UseCase-" + n, tenantId, asset.getId());
}
for (int j = 0; j < ENTITY_COUNT; j++) {
Device device = new Device(); Device device = new Device();
device.setTenantId(tenantId); device.setTenantId(tenantId);
device.setName("A" + i + "Device" + j); device.setName("A" + i + "Device" + j);
@ -550,22 +579,125 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
//TO make sure devices have different created time //TO make sure devices have different created time
Thread.sleep(1); Thread.sleep(1);
devices.add(device); devices.add(device);
er = new EntityRelation(); createRelation(tenantId, "Contains", asset.getId(), device.getId());
er.setFrom(asset.getId());
er.setTo(device.getId());
er.setType("Contains");
er.setTypeGroup(RelationTypeGroup.COMMON);
relationService.saveRelation(tenantId, er);
long temperature = (long) (Math.random() * 100); long temperature = (long) (Math.random() * 100);
temperatures.add(temperature); temperatures.add(temperature);
if (temperature > 45) { if (temperature > 45) {
highTemperatures.add(temperature); highTemperatures.add(temperature);
} }
//asset -> device : one-to-one but many edges
for (int n = 0; n < ENTITY_COUNT; n++) {
createRelation(tenantId, "UseCase-" + n, asset.getId(), device.getId());
}
}
}
//asset -> device one-to-many shared with other assets
for (int n = 0; n < devices.size(); n = n + ENTITY_COUNT) {
createRelation(tenantId, "SharedWithAsset0", assets.get(0).getId(), devices.get(n).getId());
}
createManyCustomRelationsBetweenTwoNodes(tenantId, "UseCase", assets, devices);
createHorizontalRingRelations(tenantId, "Ring(Loop)-Ast", assets);
createLoopRelations(tenantId, "Loop-Tnt-Ast-Dev", tenantId, assets.get(0).getId(), devices.get(0).getId());
createLoopRelations(tenantId, "Loop-Tnt-Ast", tenantId, assets.get(1).getId());
createLoopRelations(tenantId, "Loop-Ast-Tnt-Ast", assets.get(2).getId(), tenantId, assets.get(3).getId());
//printAllRelations();
}
private ResultSetExtractor<List<List<String>>> getListResultSetExtractor() {
return rs -> {
List<List<String>> list = new ArrayList<>();
final int columnCount = rs.getMetaData().getColumnCount();
List<String> columns = new ArrayList<>(columnCount);
for (int i = 1; i <= columnCount; i++) {
columns.add(rs.getMetaData().getColumnName(i));
}
list.add(columns);
while (rs.next()) {
List<String> data = new ArrayList<>(columnCount);
for (int i = 1; i <= columnCount; i++) {
data.add(rs.getString(i));
}
list.add(data);
} }
return list;
};
}
/*
* This useful to reproduce exact data in the PostgreSQL and play around with pgadmin query and analyze tool
* */
private void printAllRelations() {
System.out.println("" +
"DO\n" +
"$$\n" +
" DECLARE\n" +
" someint integer;\n" +
" BEGIN\n" +
" DROP TABLE IF EXISTS relation_test;\n" +
" CREATE TABLE IF NOT EXISTS relation_test\n" +
" (\n" +
" from_id uuid,\n" +
" from_type varchar(255),\n" +
" to_id uuid,\n" +
" to_type varchar(255),\n" +
" relation_type_group varchar(255),\n" +
" relation_type varchar(255),\n" +
" additional_info varchar,\n" +
" CONSTRAINT relation_test_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)\n" +
" );");
relationRepository.findAll().forEach(r ->
System.out.printf("INSERT INTO relation_test (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info)" +
" VALUES (%s, %s, %s, %s, %s, %s, %s);\n",
quote(r.getFromId()), quote(r.getFromType()), quote(r.getToId()), quote(r.getToType()),
quote(r.getRelationTypeGroup()), quote(r.getRelationType()), quote(r.getAdditionalInfo()))
);
System.out.println("" +
" END\n" +
"$$;");
}
private String quote(Object s) {
return s == null ? null : "'" + s + "'";
}
void createLoopRelations(TenantId tenantId, String type, EntityId... ids) {
assertThat("ids lenght", ids.length, Matchers.greaterThanOrEqualTo(1));
//chain all from the head to the tail
for (int i = 1; i < ids.length; i++) {
relationService.saveRelation(tenantId, new EntityRelation(ids[i - 1], ids[i], type, RelationTypeGroup.COMMON));
}
//chain tail -> head
relationService.saveRelation(tenantId, new EntityRelation(ids[ids.length - 1], ids[0], type, RelationTypeGroup.COMMON));
}
void createHorizontalRingRelations(TenantId tenantId, String type, List<Asset> assets) {
createLoopRelations(tenantId, type, assets.stream().map(Asset::getId).toArray(EntityId[]::new));
}
void createManyCustomRelationsBetweenTwoNodes(TenantId tenantId, String type, List<Asset> assets, List<Device> devices) {
for (int i = 1; i <= 5; i++) {
final String typeI = type + i;
createOneToManyRelations(tenantId, typeI, tenantId, assets.stream().map(Asset::getId).collect(Collectors.toList()));
assets.forEach(asset ->
createOneToManyRelations(tenantId, typeI, asset.getId(), devices.stream().map(Device::getId).collect(Collectors.toList())));
} }
} }
void createOneToManyRelations(TenantId tenantId, String type, EntityId from, List<EntityId> toIds) {
toIds.forEach(toId -> createRelation(tenantId, type, from, toId));
}
void createRelation(TenantId tenantId, String type, EntityId from, EntityId toId) {
relationService.saveRelation(tenantId, new EntityRelation(from, toId, type, RelationTypeGroup.COMMON));
}
@Test @Test
public void testSimpleFindEntityDataByQuery() throws InterruptedException { public void testSimpleFindEntityDataByQuery() throws InterruptedException {
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
@ -871,7 +1003,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testBuildNumericPredicateQueryOperations() throws ExecutionException, InterruptedException{ public void testBuildNumericPredicateQueryOperations() throws ExecutionException, InterruptedException {
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
List<Long> temperatures = new ArrayList<>(); List<Long> temperatures = new ArrayList<>();
@ -1031,7 +1163,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId); deviceService.deleteDevicesByTenantId(tenantId);
} }
@Test @Test
public void testFindEntityDataByQueryWithTimeseries() throws ExecutionException, InterruptedException { public void testFindEntityDataByQueryWithTimeseries() throws ExecutionException, InterruptedException {
@ -1122,7 +1254,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testBuildStringPredicateQueryOperations() throws ExecutionException, InterruptedException{ public void testBuildStringPredicateQueryOperations() throws ExecutionException, InterruptedException {
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
List<String> attributeStrings = new ArrayList<>(); List<String> attributeStrings = new ArrayList<>();
@ -1142,11 +1274,11 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
devices.add(deviceService.saveDevice(device)); devices.add(deviceService.saveDevice(device));
//TO make sure devices have different created time //TO make sure devices have different created time
Thread.sleep(1); Thread.sleep(1);
List<StringFilterPredicate.StringOperation> operationValues= Arrays.asList(StringFilterPredicate.StringOperation.values()); List<StringFilterPredicate.StringOperation> operationValues = Arrays.asList(StringFilterPredicate.StringOperation.values());
StringFilterPredicate.StringOperation operation = operationValues.get(new Random().nextInt(operationValues.size())); StringFilterPredicate.StringOperation operation = operationValues.get(new Random().nextInt(operationValues.size()));
String operationName = operation.name(); String operationName = operation.name();
attributeStrings.add(operationName); attributeStrings.add(operationName);
switch(operation){ switch (operation) {
case EQUAL: case EQUAL:
equalStrings.add(operationName); equalStrings.add(operationName);
notContainsStrings.add(operationName); notContainsStrings.add(operationName);
@ -1302,7 +1434,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testBuildStringPredicateQueryOperationsForEntityType() throws ExecutionException, InterruptedException{ public void testBuildStringPredicateQueryOperationsForEntityType() throws ExecutionException, InterruptedException {
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
@ -1419,7 +1551,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testBuildSimplePredicateQueryOperations() throws InterruptedException{ public void testBuildSimplePredicateQueryOperations() throws InterruptedException {
List<Device> devices = new ArrayList<>(); List<Device> devices = new ArrayList<>();
@ -1492,7 +1624,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
return loadedEntities; return loadedEntities;
} }
private List<KeyFilter> createStringKeyFilters(String key, EntityKeyType keyType, StringFilterPredicate.StringOperation operation, String value){ private List<KeyFilter> createStringKeyFilters(String key, EntityKeyType keyType, StringFilterPredicate.StringOperation operation, String value) {
KeyFilter filter = new KeyFilter(); KeyFilter filter = new KeyFilter();
filter.setKey(new EntityKey(keyType, key)); filter.setKey(new EntityKey(keyType, key));
StringFilterPredicate predicate = new StringFilterPredicate(); StringFilterPredicate predicate = new StringFilterPredicate();
@ -1503,7 +1635,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
return Collections.singletonList(filter); return Collections.singletonList(filter);
} }
private KeyFilter createNumericKeyFilter(String key, EntityKeyType keyType, NumericFilterPredicate.NumericOperation operation, double value){ private KeyFilter createNumericKeyFilter(String key, EntityKeyType keyType, NumericFilterPredicate.NumericOperation operation, double value) {
KeyFilter filter = new KeyFilter(); KeyFilter filter = new KeyFilter();
filter.setKey(new EntityKey(keyType, key)); filter.setKey(new EntityKey(keyType, key));
NumericFilterPredicate predicate = new NumericFilterPredicate(); NumericFilterPredicate predicate = new NumericFilterPredicate();

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java

@ -31,7 +31,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2ConfigTemplateService;
import java.util.Arrays; import java.util.Arrays;
import java.util.UUID; import java.util.UUID;
public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest { public abstract class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
@Autowired @Autowired
protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService; protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService;

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java

@ -43,7 +43,7 @@ import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class BaseOAuth2ServiceTest extends AbstractServiceTest { public abstract class BaseOAuth2ServiceTest extends AbstractServiceTest {
private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, Collections.emptyList()); private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, Collections.emptyList());
@Autowired @Autowired

2
dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java

@ -34,7 +34,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class BaseTenantProfileServiceTest extends AbstractServiceTest { public abstract class BaseTenantProfileServiceTest extends AbstractServiceTest {
private IdComparator<TenantProfile> idComparator = new IdComparator<>(); private IdComparator<TenantProfile> idComparator = new IdComparator<>();
private IdComparator<EntityInfo> tenantProfileInfoIdComparator = new IdComparator<>(); private IdComparator<EntityInfo> tenantProfileInfoIdComparator = new IdComparator<>();

33
dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java

@ -0,0 +1,33 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.service;
import org.springframework.test.context.TestPropertySource;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@TestPropertySource(locations = {"classpath:application-test.properties", "classpath:psql-test.properties"})
public @interface DaoPostgreSqlTest {
}

8
dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceSqlTest.java → dao/src/test/java/org/thingsboard/server/dao/service/psql/EntityServicePostgreSqlTest.java

@ -13,11 +13,11 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.thingsboard.server.dao.service.sql; package org.thingsboard.server.dao.service.psql;
import org.thingsboard.server.dao.service.BaseEntityServiceTest; import org.thingsboard.server.dao.service.BaseEntityServiceTest;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoPostgreSqlTest;
@DaoSqlTest @DaoPostgreSqlTest
public class EntityServiceSqlTest extends BaseEntityServiceTest { public class EntityServicePostgreSqlTest extends BaseEntityServiceTest {
} }

57
dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepositoryTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.sql.query;
import org.junit.Test;
import org.thingsboard.server.common.data.id.CustomerId;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock;
public class DefaultEntityQueryRepositoryTest {
/*
* This value has to be reasonable small to prevent infinite recursion as early as possible
* */
@Test
public void givenDefaultMaxLevel_whenStaticConstant_thenEqualsTo() {
assertThat(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT, equalTo(10));
}
@Test
public void givenMaxLevelZeroOrNegative_whenGetMaxLevel_thenReturnDefaultMaxLevel() {
DefaultEntityQueryRepository repo = mock(DefaultEntityQueryRepository.class);
willCallRealMethod().given(repo).getMaxLevel(anyInt());
assertThat(repo.getMaxLevel(0), equalTo(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT));
assertThat(repo.getMaxLevel(-1), equalTo(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT));
assertThat(repo.getMaxLevel(-2), equalTo(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT));
assertThat(repo.getMaxLevel(Integer.MIN_VALUE), equalTo(DefaultEntityQueryRepository.MAX_LEVEL_DEFAULT));
}
@Test
public void givenMaxLevelPositive_whenGetMaxLevel_thenValueTheSame() {
DefaultEntityQueryRepository repo = mock(DefaultEntityQueryRepository.class);
willCallRealMethod().given(repo).getMaxLevel(anyInt());
assertThat(repo.getMaxLevel(1), equalTo(1));
assertThat(repo.getMaxLevel(2), equalTo(2));
assertThat(repo.getMaxLevel(Integer.MAX_VALUE), equalTo(Integer.MAX_VALUE));
}
}

47
dao/src/test/resources/psql-test.properties

@ -0,0 +1,47 @@
database.ts.type=sql
database.ts_latest.type=sql
sql.ts_inserts_executor_type=fixed
sql.ts_inserts_fixed_thread_pool_size=200
sql.ts_key_value_partitioning=MONTHS
#
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.order_by.default_null_ordering=last
spring.jpa.properties.hibernate.jdbc.log.warnings=false
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
spring.datasource.username=postgres
spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:12.8:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
#org.postgresql.Driver
spring.datasource.hikari.maximumPoolSize=50
service.type=monolith
#database.ts.type=timescale
#database.ts.type=sql
#database.entities.type=sql
#
#sql.ts_inserts_executor_type=fixed
#sql.ts_inserts_fixed_thread_pool_size=200
#sql.ts_key_value_partitioning=MONTHS
#
#spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
#spring.jpa.show-sql=false
#spring.jpa.hibernate.ddl-auto=none
#spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect
#
#spring.datasource.username=postgres
#spring.datasource.password=postgres
#spring.datasource.url=jdbc:postgresql://localhost:5432/sqltest
#spring.datasource.driverClassName=org.postgresql.Driver
#spring.datasource.hikari.maximumPoolSize = 50
queue.core.pack-processing-timeout=3000
queue.rule-engine.pack-processing-timeout=3000
queue.rule-engine.queues[0].name=Main
queue.rule-engine.queues[0].topic=tb_rule_engine.main
queue.rule-engine.queues[0].poll-interval=25
queue.rule-engine.queues[0].partitions=3
queue.rule-engine.queues[0].pack-processing-timeout=3000
queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES
queue.rule-engine.queues[0].submit-strategy.type=BURST
sql.log_entity_queries=true

2
dao/src/test/resources/sql/system-test-psql.sql

@ -0,0 +1,2 @@
--PostgreSQL specific truncate to fit constraints
TRUNCATE TABLE device_credentials, device, device_profile, rule_node_state, rule_node, rule_chain;

17
pom.xml

@ -85,6 +85,7 @@
<jar-plugin.version>3.0.2</jar-plugin.version> <jar-plugin.version>3.0.2</jar-plugin.version>
<springfox-swagger.version>2.6.1</springfox-swagger.version> <springfox-swagger.version>2.6.1</springfox-swagger.version>
<springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version> <springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version>
<swagger-annotations.version>1.5.10</swagger-annotations.version>
<spatial4j.version>0.7</spatial4j.version> <spatial4j.version>0.7</spatial4j.version>
<jts.version>1.15.0</jts.version> <jts.version>1.15.0</jts.version>
<bouncycastle.version>1.67</bouncycastle.version> <bouncycastle.version>1.67</bouncycastle.version>
@ -1623,6 +1624,11 @@
<artifactId>springfox-swagger2</artifactId> <artifactId>springfox-swagger2</artifactId>
<version>${springfox-swagger.version}</version> <version>${springfox-swagger.version}</version>
</dependency> </dependency>
<dependency>
<groupId>io.swagger</groupId>
<artifactId>swagger-annotations</artifactId>
<version>${swagger-annotations.version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.bouncycastle</groupId> <groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId> <artifactId>bcprov-jdk15on</artifactId>
@ -1637,6 +1643,17 @@
<groupId>org.hsqldb</groupId> <groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId> <artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version> <version>${hsqldb.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>jdbc</artifactId>
<version>${testcontainers.version}</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>

16
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java

@ -39,12 +39,8 @@ public class TbNodeUtils {
private static final ObjectMapper mapper = new ObjectMapper(); private static final ObjectMapper mapper = new ObjectMapper();
private static final String METADATA_VARIABLE_TEMPLATE = "${%s}";
private static final Pattern DATA_PATTERN = Pattern.compile("(\\$\\[)(.*?)(])"); private static final Pattern DATA_PATTERN = Pattern.compile("(\\$\\[)(.*?)(])");
private static final String DATA_VARIABLE_TEMPLATE = "$[%s]";
public static <T> T convert(TbNodeConfiguration configuration, Class<T> clazz) throws TbNodeException { public static <T> T convert(TbNodeConfiguration configuration, Class<T> clazz) throws TbNodeException {
try { try {
return mapper.treeToValue(configuration.getData(), clazz); return mapper.treeToValue(configuration.getData(), clazz);
@ -80,7 +76,7 @@ public class TbNodeUtils {
} }
if (jsonNode != null && jsonNode.isValueNode()) { if (jsonNode != null && jsonNode.isValueNode()) {
result = result.replace(String.format(DATA_VARIABLE_TEMPLATE, group), jsonNode.asText()); result = result.replace(formatDataVarTemplate(group), jsonNode.asText());
} }
} }
} }
@ -106,8 +102,14 @@ public class TbNodeUtils {
} }
private static String processVar(String pattern, String key, String val) { private static String processVar(String pattern, String key, String val) {
String varPattern = String.format(METADATA_VARIABLE_TEMPLATE, key); return pattern.replace(formatMetadataVarTemplate(key), val);
return pattern.replace(varPattern, val);
} }
static String formatDataVarTemplate(String key) {
return "$[" + key + ']';
}
static String formatMetadataVarTemplate(String key) {
return "${" + key + '}';
}
} }

30
rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.api.util; package org.thingsboard.rule.engine.api.util;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.hamcrest.CoreMatchers;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -25,9 +26,15 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
public class TbNodeUtilsTest { public class TbNodeUtilsTest {
private static final String DATA_VARIABLE_TEMPLATE = "$[%s]";
private static final String METADATA_VARIABLE_TEMPLATE = "${%s}";
@Test @Test
public void testSimpleReplacement() { public void testSimpleReplacement() {
String pattern = "ABC ${metadata_key} $[data_key]"; String pattern = "ABC ${metadata_key} $[data_key]";
@ -112,4 +119,27 @@ public class TbNodeUtilsTest {
Assert.assertEquals("ABC metadata_value $[key1.key2[0].key3]", result); Assert.assertEquals("ABC metadata_value $[key1.key2[0].key3]", result);
} }
@Test
public void givenKey_whenFormatDataVarTemplate_thenReturnTheSameStringAsFormat() {
assertThat(TbNodeUtils.formatDataVarTemplate("key"), is("$[key]"));
assertThat(TbNodeUtils.formatDataVarTemplate("key"), is(String.format(DATA_VARIABLE_TEMPLATE, "key")));
assertThat(TbNodeUtils.formatDataVarTemplate(""), is("$[]"));
assertThat(TbNodeUtils.formatDataVarTemplate(""), is(String.format(DATA_VARIABLE_TEMPLATE, "")));
assertThat(TbNodeUtils.formatDataVarTemplate(null), is("$[null]"));
assertThat(TbNodeUtils.formatDataVarTemplate(null), is(String.format(DATA_VARIABLE_TEMPLATE, (String) null)));
}
@Test
public void givenKey_whenFormatMetadataVarTemplate_thenReturnTheSameStringAsFormat() {
assertThat(TbNodeUtils.formatMetadataVarTemplate("key"), is("${key}"));
assertThat(TbNodeUtils.formatMetadataVarTemplate("key"), is(String.format(METADATA_VARIABLE_TEMPLATE, "key")));
assertThat(TbNodeUtils.formatMetadataVarTemplate(""), is("${}"));
assertThat(TbNodeUtils.formatMetadataVarTemplate(""), is(String.format(METADATA_VARIABLE_TEMPLATE, "")));
assertThat(TbNodeUtils.formatMetadataVarTemplate(null), is("${null}"));
assertThat(TbNodeUtils.formatMetadataVarTemplate(null), is(String.format(METADATA_VARIABLE_TEMPLATE, (String) null)));
}
} }

10
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNodeConfiguration.java

@ -25,10 +25,18 @@ public class TbClearAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigurat
@Override @Override
public TbClearAlarmNodeConfiguration defaultConfiguration() { public TbClearAlarmNodeConfiguration defaultConfiguration() {
TbClearAlarmNodeConfiguration configuration = new TbClearAlarmNodeConfiguration(); TbClearAlarmNodeConfiguration configuration = new TbClearAlarmNodeConfiguration();
configuration.setAlarmDetailsBuildJs("var details = {};\n" + configuration.setAlarmDetailsBuildJs("" +
"//***DO NOT CHANGE THIS LINES***\n" +
"var details = {};\n" +
"if (metadata.prevAlarmDetails) {\n" + "if (metadata.prevAlarmDetails) {\n" +
" details = JSON.parse(metadata.prevAlarmDetails);\n" + " details = JSON.parse(metadata.prevAlarmDetails);\n" +
" //remove prevAlarmDetails from metadata\n" +
" delete metadata.prevAlarmDetails;\n" +
" //now metadata is the same as it comes IN this rule node" +
"}\n" + "}\n" +
"//***PLACE YOUR CODE BELOW***\n" +
"\n" +
"\n" +
"return details;"); "return details;");
configuration.setAlarmType("General Alarm"); configuration.setAlarmType("General Alarm");
return configuration; return configuration;

22
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java

@ -15,7 +15,6 @@
*/ */
package org.thingsboard.rule.engine.metadata; package org.thingsboard.rule.engine.metadata;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonWriteFeature; import com.fasterxml.jackson.core.json.JsonWriteFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -35,6 +34,7 @@ import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
@ -50,7 +50,6 @@ import java.util.stream.Collectors;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL; import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_FIRST; import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_FIRST;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE; import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE;
import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
/** /**
* Created by mshvayka on 04.09.18. * Created by mshvayka on 04.09.18.
@ -64,6 +63,7 @@ import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
"If selected fetch mode <b>ALL</b> Telemetry will be added like array into Message Metadata where <b>key</b> is Timestamp and <b>value</b> is value of Telemetry.</br>" + "If selected fetch mode <b>ALL</b> Telemetry will be added like array into Message Metadata where <b>key</b> is Timestamp and <b>value</b> is value of Telemetry.</br>" +
"If selected fetch mode <b>FIRST</b> or <b>LAST</b> Telemetry will be added like string without Timestamp.</br>" + "If selected fetch mode <b>FIRST</b> or <b>LAST</b> Telemetry will be added like string without Timestamp.</br>" +
"Also, the rule node allows you to select telemetry sampling order: <b>ASC</b> or <b>DESC</b>. </br>" + "Also, the rule node allows you to select telemetry sampling order: <b>ASC</b> or <b>DESC</b>. </br>" +
"Aggregation feature allows you to fetch aggregated telemetry as a single value by <b>AVG, COUNT, SUM, MIN, MAX, NONE</b>. </br>" +
"<b>Note</b>: The maximum size of the fetched array is 1000 records.\n ", "<b>Note</b>: The maximum size of the fetched array is 1000 records.\n ",
uiResources = {"static/rulenode/rulenode-core-config.js"}, uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase") configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase")
@ -78,6 +78,7 @@ public class TbGetTelemetryNode implements TbNode {
private ObjectMapper mapper; private ObjectMapper mapper;
private String fetchMode; private String fetchMode;
private String orderByFetchAll; private String orderByFetchAll;
private Aggregation aggregation;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
@ -89,11 +90,20 @@ public class TbGetTelemetryNode implements TbNode {
if (StringUtils.isEmpty(orderByFetchAll)) { if (StringUtils.isEmpty(orderByFetchAll)) {
orderByFetchAll = ASC_ORDER; orderByFetchAll = ASC_ORDER;
} }
aggregation = parseAggregationConfig(config.getAggregation());
mapper = new ObjectMapper(); mapper = new ObjectMapper();
mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false); mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
} }
Aggregation parseAggregationConfig(String aggName) {
if (StringUtils.isEmpty(aggName)) {
return Aggregation.NONE;
}
return Aggregation.valueOf(aggName);
}
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
if (tsKeyNames.isEmpty()) { if (tsKeyNames.isEmpty()) {
@ -120,8 +130,14 @@ public class TbGetTelemetryNode implements TbNode {
} }
private List<ReadTsKvQuery> buildQueries(TbMsg msg, List<String> keys) { private List<ReadTsKvQuery> buildQueries(TbMsg msg, List<String> keys) {
final Interval interval = getInterval(msg);
final long aggIntervalStep = Aggregation.NONE.equals(aggregation) ? 1 :
// exact how it validates on BaseTimeseriesService.validate()
// see CassandraBaseTimeseriesDao.findAllAsync()
interval.getEndTs() - interval.getStartTs();
return keys.stream() return keys.stream()
.map(key -> new BaseReadTsKvQuery(key, getInterval(msg).getStartTs(), getInterval(msg).getEndTs(), 1, limit, NONE, getOrderBy())) .map(key -> new BaseReadTsKvQuery(key, interval.getStartTs(), interval.getEndTs(), aggIntervalStep, limit, aggregation, getOrderBy()))
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java

@ -17,6 +17,7 @@ package org.thingsboard.rule.engine.metadata;
import lombok.Data; import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.kv.Aggregation;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -46,6 +47,7 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetT
private String endIntervalTimeUnit; private String endIntervalTimeUnit;
private String fetchMode; //FIRST, LAST, ALL private String fetchMode; //FIRST, LAST, ALL
private String orderBy; //ASC, DESC private String orderBy; //ASC, DESC
private String aggregation; //MIN, MAX, AVG, SUM, COUNT, NONE;
private int limit; private int limit;
private List<String> latestTsKeyNames; private List<String> latestTsKeyNames;
@ -63,6 +65,7 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetT
configuration.setStartIntervalPattern(""); configuration.setStartIntervalPattern("");
configuration.setEndIntervalPattern(""); configuration.setEndIntervalPattern("");
configuration.setOrderBy("ASC"); configuration.setOrderBy("ASC");
configuration.setAggregation(Aggregation.NONE.name());
configuration.setLimit(MAX_FETCH_SIZE); configuration.setLimit(MAX_FETCH_SIZE);
return configuration; return configuration;
} }

12
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java

@ -78,7 +78,7 @@ public class TbHttpClient {
private AsyncRestTemplate httpClient; private AsyncRestTemplate httpClient;
private Deque<ListenableFuture<ResponseEntity<String>>> pendingFutures; private Deque<ListenableFuture<ResponseEntity<String>>> pendingFutures;
TbHttpClient(TbRestApiCallNodeConfiguration config) throws TbNodeException { TbHttpClient(TbRestApiCallNodeConfiguration config, EventLoopGroup eventLoopGroupShared) throws TbNodeException {
try { try {
this.config = config; this.config = config;
if (config.getMaxParallelRequestsCount() > 0) { if (config.getMaxParallelRequestsCount() > 0) {
@ -139,8 +139,7 @@ public class TbHttpClient {
} }
httpClient = new AsyncRestTemplate(); httpClient = new AsyncRestTemplate();
} else { } else {
this.eventLoopGroup = new NioEventLoopGroup(); Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(getSharedOrCreateEventLoopGroup(eventLoopGroupShared));
Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup);
nettyFactory.setSslContext(config.getCredentials().initSslContext()); nettyFactory.setSslContext(config.getCredentials().initSslContext());
nettyFactory.setReadTimeout(config.getReadTimeoutMs()); nettyFactory.setReadTimeout(config.getReadTimeoutMs());
httpClient = new AsyncRestTemplate(nettyFactory); httpClient = new AsyncRestTemplate(nettyFactory);
@ -150,6 +149,13 @@ public class TbHttpClient {
} }
} }
EventLoopGroup getSharedOrCreateEventLoopGroup(EventLoopGroup eventLoopGroupShared) {
if (eventLoopGroupShared != null) {
return eventLoopGroupShared;
}
return this.eventLoopGroup = new NioEventLoopGroup();
}
private void checkSystemProxyProperties() throws TbNodeException { private void checkSystemProxyProperties() throws TbNodeException {
boolean useHttpProxy = !StringUtils.isEmpty(System.getProperty("http.proxyHost")) && !StringUtils.isEmpty(System.getProperty("http.proxyPort")); boolean useHttpProxy = !StringUtils.isEmpty(System.getProperty("http.proxyHost")) && !StringUtils.isEmpty(System.getProperty("http.proxyPort"));
boolean useHttpsProxy = !StringUtils.isEmpty(System.getProperty("https.proxyHost")) && !StringUtils.isEmpty(System.getProperty("https.proxyPort")); boolean useHttpsProxy = !StringUtils.isEmpty(System.getProperty("https.proxyHost")) && !StringUtils.isEmpty(System.getProperty("https.proxyPort"));

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java

@ -51,7 +51,7 @@ public class TbRestApiCallNode implements TbNode {
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
TbRestApiCallNodeConfiguration config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class); TbRestApiCallNodeConfiguration config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class);
httpClient = new TbHttpClient(config); httpClient = new TbHttpClient(config, ctx.getSharedEventLoop());
useRedisQueueForMsgPersistence = config.isUseRedisQueueForMsgPersistence(); useRedisQueueForMsgPersistence = config.isUseRedisQueueForMsgPersistence();
if (useRedisQueueForMsgPersistence) { if (useRedisQueueForMsgPersistence) {
log.warn("[{}][{}] Usage of Redis Template is deprecated starting 2.5 and will have no affect", ctx.getTenantId(), ctx.getSelfId()); log.warn("[{}][{}] Usage of Redis Template is deprecated starting 2.5 and will have no affect", ctx.getTenantId(), ctx.getSelfId());

68
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java

@ -0,0 +1,68 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.metadata;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.server.common.data.kv.Aggregation;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock;
public class TbGetTelemetryNodeTest {
TbGetTelemetryNode node;
@Before
public void setUp() throws Exception {
node = mock(TbGetTelemetryNode.class);
willCallRealMethod().given(node).parseAggregationConfig(any());
}
@Test
public void givenAggregationAsString_whenParseAggregation_thenReturnEnum() {
//compatibility with old configs without "aggregation" parameter
assertThat(node.parseAggregationConfig(null), is(Aggregation.NONE));
assertThat(node.parseAggregationConfig(""), is(Aggregation.NONE));
//common values
assertThat(node.parseAggregationConfig("MIN"), is(Aggregation.MIN));
assertThat(node.parseAggregationConfig("MAX"), is(Aggregation.MAX));
assertThat(node.parseAggregationConfig("AVG"), is(Aggregation.AVG));
assertThat(node.parseAggregationConfig("SUM"), is(Aggregation.SUM));
assertThat(node.parseAggregationConfig("COUNT"), is(Aggregation.COUNT));
assertThat(node.parseAggregationConfig("NONE"), is(Aggregation.NONE));
//all possible values in future
for (Aggregation aggEnum : Aggregation.values()) {
assertThat(node.parseAggregationConfig(aggEnum.name()), is(aggEnum));
}
}
@Test(expected = IllegalArgumentException.class)
public void givenAggregationWhiteSpace_whenParseAggregation_thenException() {
node.parseAggregationConfig(" ");
}
@Test(expected = IllegalArgumentException.class)
public void givenAggregationIncorrect_whenParseAggregation_thenException() {
node.parseAggregationConfig("TOP");
}
}

61
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java

@ -0,0 +1,61 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.rest;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.is;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willCallRealMethod;
import static org.mockito.Mockito.mock;
public class TbHttpClientTest {
EventLoopGroup eventLoop;
TbHttpClient client;
@Before
public void setUp() throws Exception {
client = mock(TbHttpClient.class);
willCallRealMethod().given(client).getSharedOrCreateEventLoopGroup(any());
}
@After
public void tearDown() throws Exception {
if (eventLoop != null) {
eventLoop.shutdownGracefully();
}
}
@Test
public void givenSharedEventLoop_whenGetEventLoop_ThenReturnShared() {
eventLoop = mock(EventLoopGroup.class);
assertThat(client.getSharedOrCreateEventLoopGroup(eventLoop), is(eventLoop));
}
@Test
public void givenNull_whenGetEventLoop_ThenReturnShared() {
eventLoop = client.getSharedOrCreateEventLoopGroup(null);
assertThat(eventLoop, instanceOf(NioEventLoopGroup.class));
}
}
Loading…
Cancel
Save