diff --git a/application/pom.xml b/application/pom.xml
index f454a462b9..3378d52459 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -360,6 +360,7 @@
**/sql/*Test.java
+ **/psql/*Test.java
**/nosql/*Test.java
diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
index f1c432a532..a12887db81 100644
--- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
@@ -731,6 +731,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
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!");
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 78dbadc9bf..8971cf1a23 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -190,13 +190,13 @@ class DefaultTbContext implements TbContext {
@Override
public void enqueueForTellNext(TbMsg tbMsg, String queueName, String relationType, Runnable onSuccess, Consumer onFailure) {
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
public void enqueueForTellNext(TbMsg tbMsg, String queueName, Set relationTypes, Runnable onSuccess, Consumer onFailure) {
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) {
@@ -211,9 +211,13 @@ class DefaultTbContext implements TbContext {
}
private void enqueueForTellNext(TopicPartitionInfo tpi, TbMsg source, Set relationTypes, String failureMessage, Runnable onSuccess, Consumer onFailure) {
+ enqueueForTellNext(tpi, source.getQueueName(), source, relationTypes, failureMessage, onSuccess, onFailure);
+ }
+
+ private void enqueueForTellNext(TopicPartitionInfo tpi, String queueName, TbMsg source, Set relationTypes, String failureMessage, Runnable onSuccess, Consumer onFailure) {
RuleChainId ruleChainId = nodeCtx.getSelf().getRuleChainId();
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()
.setTenantIdMSB(getTenantId().getId().getMostSignificantBits())
.setTenantIdLSB(getTenantId().getId().getLeastSignificantBits())
diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
index d8406088c7..e896243f04 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java
@@ -16,6 +16,8 @@
package org.thingsboard.server.controller;
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.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@@ -44,6 +46,7 @@ import org.thingsboard.server.service.update.UpdateService;
@RequestMapping("/api/admin")
public class AdminController extends BaseController {
+ public static final String SYS_ADMIN_AUTHORITY_ONLY = " Available for users with System Administrator ('SYS_ADMIN') authority only.";
@Autowired
private MailService mailService;
@@ -59,10 +62,14 @@ public class AdminController extends BaseController {
@Autowired
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')")
@RequestMapping(value = "/settings/{key}", method = RequestMethod.GET)
@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 {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
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')")
@RequestMapping(value = "/settings", method = RequestMethod.POST)
@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 {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE);
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')")
@RequestMapping(value = "/securitySettings", method = RequestMethod.GET)
@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')")
@RequestMapping(value = "/securitySettings", method = RequestMethod.POST)
@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 {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.WRITE);
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')")
@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 {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
adminSettings = checkNotNull(adminSettings);
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"));
((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')")
@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 {
accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ);
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')")
@RequestMapping(value = "/updates", method = RequestMethod.GET)
@ResponseBody
diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
index d63674b7ee..94c741c774 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
@@ -16,9 +16,12 @@
package org.thingsboard.server.controller;
import com.google.common.util.concurrent.ListenableFuture;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
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.audit.ActionType;
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.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
@@ -61,9 +64,8 @@ import java.util.ArrayList;
import java.util.List;
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.dao.asset.BaseAssetService.TB_SERVICE_QUEUE;
@RestController
@TbCoreComponent
@@ -75,10 +77,15 @@ public class AssetController extends BaseController {
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')")
@RequestMapping(value = "/asset/{assetId}", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/asset/info/{assetId}", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/asset", method = RequestMethod.POST)
@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 {
if (TB_SERVICE_QUEUE.equals(asset.getType())) {
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')")
@RequestMapping(value = "/asset/{assetId}", method = RequestMethod.DELETE)
@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);
try {
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')")
@RequestMapping(value = "/customer/{customerId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody
- public Asset assignAssetToCustomer(@PathVariable("customerId") String strCustomerId,
- @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
+ public Asset assignAssetToCustomer(@ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) @PathVariable("customerId") String strCustomerId,
+ @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter("customerId", strCustomerId);
checkParameter(ASSET_ID, strAssetId);
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')")
@RequestMapping(value = "/customer/asset/{assetId}", method = RequestMethod.DELETE)
@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);
try {
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')")
@RequestMapping(value = "/customer/public/asset/{assetId}", method = RequestMethod.POST)
@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);
try {
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')")
@RequestMapping(value = "/tenant/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getTenantAssets(
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page,
+ @ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_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 {
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')")
@RequestMapping(value = "/tenant/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getTenantAssetInfos(
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page,
+ @ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_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 {
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')")
@RequestMapping(value = "/tenant/assets", params = {"assetName"}, method = RequestMethod.GET)
@ResponseBody
public Asset getTenantAsset(
+ @ApiParam(value = ASSET_NAME_DESCRIPTION)
@RequestParam String assetName) throws ThingsboardException {
try {
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')")
@RequestMapping(value = "/customer/{customerId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getCustomerAssets(
+ @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable("customerId") String strCustomerId,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page,
+ @ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_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 {
checkParameter("customerId", strCustomerId);
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')")
@RequestMapping(value = "/customer/{customerId}/assetInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getCustomerAssetInfos(
+ @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION)
@PathVariable("customerId") String strCustomerId,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page,
+ @ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_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 {
checkParameter("customerId", strCustomerId);
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')")
@RequestMapping(value = "/assets", params = {"assetIds"}, method = RequestMethod.GET)
@ResponseBody
public List getAssetsByIds(
+ @ApiParam(value = "A list of assets ids, separated by comma ','")
@RequestParam("assetIds") String[] strAssetIds) throws ThingsboardException {
checkArrayParameter("assetIds", strAssetIds);
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')")
@RequestMapping(value = "/assets", method = RequestMethod.POST)
@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')")
@RequestMapping(value = "/asset/types", method = RequestMethod.GET)
@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')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.POST)
@ResponseBody
- public Asset assignAssetToEdge(@PathVariable(EDGE_ID) String strEdgeId,
- @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
+ public Asset assignAssetToEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId,
+ @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
checkParameter(ASSET_ID, strAssetId);
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')")
@RequestMapping(value = "/edge/{edgeId}/asset/{assetId}", method = RequestMethod.DELETE)
@ResponseBody
- public Asset unassignAssetFromEdge(@PathVariable(EDGE_ID) String strEdgeId,
- @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
+ public Asset unassignAssetFromEdge(@ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) @PathVariable(EDGE_ID) String strEdgeId,
+ @ApiParam(value = ASSET_ID_PARAM_DESCRIPTION) @PathVariable(ASSET_ID) String strAssetId) throws ThingsboardException {
checkParameter(EDGE_ID, strEdgeId);
checkParameter(ASSET_ID, strAssetId);
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')")
@RequestMapping(value = "/edge/{edgeId}/assets", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getEdgeAssets(
+ @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION)
@PathVariable(EDGE_ID) String strEdgeId,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION)
@RequestParam int page,
+ @ApiParam(value = ASSET_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = ASSET_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = ASSET_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder,
+ @ApiParam(value = "Timestamp. Assets with creation time before it won't be queried")
@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 {
checkParameter(EDGE_ID, strEdgeId);
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')")
@PostMapping("/asset/bulk_import")
public BulkImportResult processAssetsBulkImport(@RequestBody BulkImportRequest request) throws Exception {
diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java
index 3c4db474ef..ca982dbd3f 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java
+++ b/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.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
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.dao.audit.AuditLogService;
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.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.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
@@ -74,9 +81,13 @@ public class AuthController extends BaseController {
private final AuditLogService auditLogService;
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()")
@RequestMapping(value = "/auth/user", method = RequestMethod.GET)
- public @ResponseBody User getUser() throws ThingsboardException {
+ public @ResponseBody
+ User getUser() throws ThingsboardException {
try {
SecurityUser securityUser = getCurrentUser();
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()")
@RequestMapping(value = "/auth/logout", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
@@ -92,13 +105,17 @@ public class AuthController extends BaseController {
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()")
@RequestMapping(value = "/auth/changePassword", method = RequestMethod.POST)
@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 {
- String currentPassword = changePasswordRequest.get("currentPassword").asText();
- String newPassword = changePasswordRequest.get("newPassword").asText();
+ String currentPassword = changePasswordRequest.getCurrentPassword();
+ String newPassword = changePasswordRequest.getNewPassword();
SecurityUser securityUser = getCurrentUser();
UserCredentials userCredentials = userService.findUserCredentialsByUserId(TenantId.SYS_TENANT_ID, securityUser.getId());
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)
@ResponseBody
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)
public ResponseEntity checkActivateToken(
+ @ApiParam(value = "The activate token string.")
@RequestParam(value = "activateToken") String activateToken) {
HttpHeaders headers = new HttpHeaders();
HttpStatus responseStatus;
@@ -157,13 +181,17 @@ public class AuthController extends BaseController {
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)
@ResponseStatus(value = HttpStatus.OK)
public void requestResetPasswordByEmail(
- @RequestBody JsonNode resetPasswordByEmailRequest,
+ @ApiParam(value = "The JSON object representing the reset password email request.")
+ @RequestBody ResetPasswordEmailRequest resetPasswordByEmailRequest,
HttpServletRequest request) throws ThingsboardException {
try {
- String email = resetPasswordByEmailRequest.get("email").asText();
+ String email = resetPasswordByEmailRequest.getEmail();
UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email);
User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId());
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)
public ResponseEntity checkResetToken(
+ @ApiParam(value = "The reset token string.")
@RequestParam(value = "resetToken") String resetToken) {
HttpHeaders headers = new HttpHeaders();
HttpStatus responseStatus;
@@ -198,16 +231,24 @@ public class AuthController extends BaseController {
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)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
- public JsonNode activateUser(
- @RequestBody JsonNode activateRequest,
+ public JwtTokenPair activateUser(
+ @ApiParam(value = "Activate user request.")
+ @RequestBody ActivateUserRequest activateRequest,
@RequestParam(required = false, defaultValue = "true") boolean sendActivationMail,
HttpServletRequest request) throws ThingsboardException {
try {
- String activateToken = activateRequest.get("activateToken").asText();
- String password = activateRequest.get("password").asText();
+ String activateToken = activateRequest.getActivateToken();
+ String password = activateRequest.getPassword();
systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, null);
String encodedPassword = passwordEncoder.encode(password);
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 refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
- ObjectMapper objectMapper = new ObjectMapper();
- ObjectNode tokenObject = objectMapper.createObjectNode();
- tokenObject.put("token", accessToken.getToken());
- tokenObject.put("refreshToken", refreshToken.getToken());
- return tokenObject;
+ return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken());
} catch (Exception 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)
@ResponseStatus(value = HttpStatus.OK)
@ResponseBody
- public JsonNode resetPassword(
- @RequestBody JsonNode resetPasswordRequest,
+ public JwtTokenPair resetPassword(
+ @ApiParam(value = "Reset password request.")
+ @RequestBody ResetPasswordRequest resetPasswordRequest,
HttpServletRequest request) throws ThingsboardException {
try {
- String resetToken = resetPasswordRequest.get("resetToken").asText();
- String password = resetPasswordRequest.get("password").asText();
+ String resetToken = resetPasswordRequest.getResetToken();
+ String password = resetPasswordRequest.getPassword();
UserCredentials userCredentials = userService.findUserCredentialsByResetToken(TenantId.SYS_TENANT_ID, resetToken);
if (userCredentials != null) {
systemSecurityService.validatePassword(TenantId.SYS_TENANT_ID, password, userCredentials);
@@ -273,11 +315,7 @@ public class AuthController extends BaseController {
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
- ObjectMapper objectMapper = new ObjectMapper();
- ObjectNode tokenObject = objectMapper.createObjectNode();
- tokenObject.put("token", accessToken.getToken());
- tokenObject.put("refreshToken", refreshToken.getToken());
- return tokenObject;
+ return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken());
} else {
throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index f320b8eafa..f1e8f06d5e 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -153,6 +153,46 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@TbCoreComponent
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 ";
protected static final String DEFAULT_DASHBOARD = "defaultDashboardId";
protected static final String HOME_DASHBOARD = "homeDashboardId";
@@ -897,7 +937,7 @@ public abstract class BaseController {
PageDataIterableByTenantIdEntityId relatedEdgeIdsIterator =
new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE);
List result = new ArrayList<>();
- for(EdgeId edgeId : relatedEdgeIdsIterator) {
+ for (EdgeId edgeId : relatedEdgeIdsIterator) {
result.add(edgeId);
}
return result;
diff --git a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
index 81676374f8..a2bb902dad 100644
--- a/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/ComponentDescriptorController.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.server.controller;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@@ -38,10 +40,20 @@ import java.util.Set;
@RequestMapping("/api")
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')")
@RequestMapping(value = "/component/{componentDescriptorClazz:.+}", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/components/{componentType}", method = RequestMethod.GET)
@ResponseBody
- public List getComponentDescriptorsByType(@PathVariable("componentType") String strComponentType,
- @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
+ public List getComponentDescriptorsByType(
+ @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);
try {
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')")
@RequestMapping(value = "/components", params = {"componentTypes"}, method = RequestMethod.GET)
@ResponseBody
- public List getComponentDescriptorsByTypes(@RequestParam("componentTypes") String[] strComponentTypes,
- @RequestParam(value = "ruleChainType", required = false) String strRuleChainType) throws ThingsboardException {
+ public List getComponentDescriptorsByTypes(
+ @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);
try {
Set componentTypes = new HashSet<>();
diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
index 3e58e0656e..335140949a 100644
--- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java
+++ b/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.ObjectMapper;
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.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@@ -50,18 +52,23 @@ import java.util.List;
@RequestMapping("/api")
public class CustomerController extends BaseController {
- public static final String CUSTOMER_ID = "customerId";
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')")
@RequestMapping(value = "/customer/{customerId}", method = RequestMethod.GET)
@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);
try {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
Customer customer = checkCustomerId(customerId, Operation.READ);
- if(!customer.getAdditionalInfo().isNull()) {
+ if (!customer.getAdditionalInfo().isNull()) {
processDashboardIdFromAdditionalInfo((ObjectNode) customer.getAdditionalInfo(), HOME_DASHBOARD);
}
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')")
@RequestMapping(value = "/customer/{customerId}/shortInfo", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/customer/{customerId}/title", method = RequestMethod.GET, produces = "application/text")
@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);
try {
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')")
@RequestMapping(value = "/customer", method = RequestMethod.POST)
@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 {
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')")
@RequestMapping(value = "/customer/{customerId}", method = RequestMethod.DELETE)
@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);
try {
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')")
@RequestMapping(value = "/customers", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
- public PageData getCustomers(@RequestParam int pageSize,
- @RequestParam int page,
- @RequestParam(required = false) String textSearch,
- @RequestParam(required = false) String sortProperty,
- @RequestParam(required = false) String sortOrder) throws ThingsboardException {
+ public PageData getCustomers(
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
+ @RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
+ @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 {
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
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')")
@RequestMapping(value = "/tenant/customers", params = {"customerTitle"}, method = RequestMethod.GET)
@ResponseBody
public Customer getTenantCustomer(
+ @ApiParam(value = "A string value representing the Customer title.")
@RequestParam String customerTitle) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
diff --git a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java b/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
index f219889403..3f87a321d8 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DashboardController.java
+++ b/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.node.ObjectNode;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
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_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}")
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')")
@RequestMapping(value = "/dashboard/serverTime", method = RequestMethod.GET)
@ResponseBody
@@ -80,6 +88,11 @@ public class DashboardController extends BaseController {
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')")
@RequestMapping(value = "/dashboard/maxDatapointsLimit", method = RequestMethod.GET)
@ResponseBody
@@ -87,10 +100,16 @@ public class DashboardController extends BaseController {
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')")
@RequestMapping(value = "/dashboard/info/{dashboardId}", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/dashboard", method = RequestMethod.POST)
@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 {
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')")
@RequestMapping(value = "/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@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);
try {
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')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.POST)
@ResponseBody
- public Dashboard assignDashboardToCustomer(@PathVariable("customerId") String strCustomerId,
- @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
- checkParameter("customerId", strCustomerId);
+ public Dashboard assignDashboardToCustomer(
+ @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(CUSTOMER_ID, strCustomerId);
checkParameter(DASHBOARD_ID, strDashboardId);
try {
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')")
@RequestMapping(value = "/customer/{customerId}/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@ResponseBody
- public Dashboard unassignDashboardFromCustomer(@PathVariable("customerId") String strCustomerId,
- @PathVariable(DASHBOARD_ID) String strDashboardId) throws ThingsboardException {
+ public Dashboard unassignDashboardFromCustomer(
+ @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(DASHBOARD_ID, strDashboardId);
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')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers", method = RequestMethod.POST)
@ResponseBody
- public Dashboard updateDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
- @RequestBody(required = false) String[] strCustomerIds) throws ThingsboardException {
+ public Dashboard updateDashboardCustomers(
+ @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);
try {
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')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/add", method = RequestMethod.POST)
@ResponseBody
- public Dashboard addDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
- @RequestBody String[] strCustomerIds) throws ThingsboardException {
+ public Dashboard addDashboardCustomers(
+ @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);
try {
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')")
@RequestMapping(value = "/dashboard/{dashboardId}/customers/remove", method = RequestMethod.POST)
@ResponseBody
- public Dashboard removeDashboardCustomers(@PathVariable(DASHBOARD_ID) String strDashboardId,
- @RequestBody String[] strCustomerIds) throws ThingsboardException {
+ public Dashboard removeDashboardCustomers(
+ @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);
try {
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')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
@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);
try {
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')")
@RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.DELETE)
@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);
try {
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')")
@RequestMapping(value = "/tenant/{tenantId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData 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,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_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 {
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')")
@RequestMapping(value = "/tenant/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getTenantDashboards(
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = HIDDEN_FOR_MOBILE)
@RequestParam(required = false) Boolean mobile,
+ @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_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 {
TenantId tenantId = getCurrentUser().getTenantId();
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
- if (mobile != null && mobile.booleanValue()) {
+ if (mobile != null && mobile) {
return checkNotNull(dashboardService.findMobileDashboardsByTenantId(tenantId, pageLink));
} else {
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')")
@RequestMapping(value = "/customer/{customerId}/dashboards", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData 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,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = HIDDEN_FOR_MOBILE)
@RequestParam(required = false) Boolean mobile,
+ @ApiParam(value = DASHBOARD_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DASHBOARD_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 {
- checkParameter("customerId", strCustomerId);
+ checkParameter(CUSTOMER_ID, strCustomerId);
try {
TenantId tenantId = getCurrentUser().getTenantId();
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId, Operation.READ);
PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder);
- if (mobile != null && mobile.booleanValue()) {
+ if (mobile != null && mobile) {
return checkNotNull(dashboardService.findMobileDashboardsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
} else {
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()")
@RequestMapping(value = "/dashboard/home", method = RequestMethod.GET)
@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()")
@RequestMapping(value = "/dashboard/home/info", method = RequestMethod.GET)
@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')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.GET)
@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')")
@RequestMapping(value = "/tenant/dashboard/home/info", method = RequestMethod.POST)
@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 {
if (homeDashboardInfo.getDashboardId() != null) {
checkDashboardId(homeDashboardInfo.getDashboardId(), Operation.READ);
@@ -635,7 +783,8 @@ public class DashboardController extends BaseController {
}
return new HomeDashboardInfo(dashboardId, hideDashboardToolbar);
}
- } catch (Exception e) {}
+ } catch (Exception e) {
+ }
return null;
}
@@ -651,7 +800,8 @@ public class DashboardController extends BaseController {
}
return new HomeDashboard(dashboard, hideDashboardToolbar);
}
- } catch (Exception e) {}
+ } catch (Exception e) {
+ }
return null;
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index 8cfa5ee2cf..f0991ff901 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/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.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
@@ -91,16 +93,22 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID;
@RequiredArgsConstructor
@Slf4j
public class DeviceController extends BaseController {
- private final DeviceBulkImportService deviceBulkImportService;
- private static final String DEVICE_ID = "deviceId";
- private static final String DEVICE_NAME = "deviceName";
- private static final String TENANT_ID = "tenantId";
+ protected static final String DEVICE_ID = "deviceId";
+ protected static final String DEVICE_NAME = "deviceName";
+ 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')")
@RequestMapping(value = "/device/{deviceId}", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/device/info/{deviceId}", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/device", method = RequestMethod.POST)
@ResponseBody
- public Device saveDevice(@RequestBody Device device,
- @RequestParam(name = "accessToken", required = false) String accessToken) throws ThingsboardException {
+ public Device saveDevice(@ApiParam(value = "A JSON value representing the device.") @RequestBody Device device,
+ @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;
try {
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')")
@RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE)
@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);
try {
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')")
@RequestMapping(value = "/customer/{customerId}/device/{deviceId}", method = RequestMethod.POST)
@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 {
checkParameter("customerId", strCustomerId);
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')")
@RequestMapping(value = "/customer/device/{deviceId}", method = RequestMethod.DELETE)
@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);
try {
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')")
@RequestMapping(value = "/customer/public/device/{deviceId}", method = RequestMethod.POST)
@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);
try {
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')")
@RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET)
@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);
try {
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')")
@RequestMapping(value = "/device/credentials", method = RequestMethod.POST)
@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);
try {
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')")
@RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getTenantDevices(
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_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 {
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')")
@RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getTenantDeviceInfos(
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = DEVICE_PROFILE_ID_DESCRIPTION)
@RequestParam(required = false) String deviceProfileId,
+ @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@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 sortOrder) throws ThingsboardException {
+ @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
+ @RequestParam(required = false) String sortOrder
+ ) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
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')")
@RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET)
@ResponseBody
public Device getTenantDevice(
+ @ApiParam(value = DEVICE_NAME_DESCRIPTION)
@RequestParam String deviceName) throws ThingsboardException {
try {
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')")
@RequestMapping(value = "/customer/{customerId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getCustomerDevices(
+ @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("customerId") String strCustomerId,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_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 {
checkParameter("customerId", strCustomerId);
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')")
@RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getCustomerDeviceInfos(
+ @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("customerId") String strCustomerId,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = DEVICE_PROFILE_ID_DESCRIPTION)
@RequestParam(required = false) String deviceProfileId,
+ @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_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 {
checkParameter("customerId", strCustomerId);
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')")
@RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET)
@ResponseBody
public List getDevicesByIds(
+ @ApiParam(value = "A list of devices ids, separated by comma ','")
@RequestParam("deviceIds") String[] strDeviceIds) throws ThingsboardException {
checkArrayParameter("deviceIds", strDeviceIds);
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')")
@RequestMapping(value = "/devices", method = RequestMethod.POST)
@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')")
@RequestMapping(value = "/device/types", method = RequestMethod.GET)
@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')")
@RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.POST)
@ResponseBody
- public DeferredResult claimDevice(@PathVariable(DEVICE_NAME) String deviceName,
+ public DeferredResult 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 {
checkParameter(DEVICE_NAME, deviceName);
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')")
@RequestMapping(value = "/customer/device/{deviceName}/claim", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
- public DeferredResult reClaimDevice(@PathVariable(DEVICE_NAME) String deviceName) throws ThingsboardException {
+ public DeferredResult 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);
try {
final DeferredResult deferredResult = new DeferredResult<>();
@@ -615,10 +725,14 @@ public class DeviceController extends BaseController {
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')")
@RequestMapping(value = "/tenant/{tenantId}/device/{deviceId}", method = RequestMethod.POST)
@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 {
checkParameter(TENANT_ID, strTenantId);
checkParameter(DEVICE_ID, strDeviceId);
@@ -665,10 +779,16 @@ public class DeviceController extends BaseController {
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')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.POST)
@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 {
checkParameter(EDGE_ID, strEdgeId);
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')")
@RequestMapping(value = "/edge/{edgeId}/device/{deviceId}", method = RequestMethod.DELETE)
@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 {
checkParameter(EDGE_ID, strEdgeId);
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')")
@RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET)
@ResponseBody
public PageData getEdgeDevices(
+ @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable(EDGE_ID) String strEdgeId,
+ @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
@RequestParam int pageSize,
+ @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
@RequestParam int page,
+ @ApiParam(value = DEVICE_TYPE_DESCRIPTION)
@RequestParam(required = false) String type,
+ @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION)
@RequestParam(required = false) String textSearch,
+ @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = DEVICE_SORT_PROPERTY_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortProperty,
+ @ApiParam(value = SORT_ORDER_DESCRIPTION, allowableValues = SORT_ORDER_ALLOWABLE_VALUES)
@RequestParam(required = false) String sortOrder,
+ @ApiParam(value = "Timestamp. Devices with creation time before it won't be queried")
@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 {
checkParameter(EDGE_ID, strEdgeId);
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')")
@RequestMapping(value = "/devices/count/{otaPackageType}/{deviceProfileId}", method = RequestMethod.GET)
@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 {
checkParameter("OtaPackageType", otaPackageType);
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')")
@PostMapping("/device/bulk_import")
public BulkImportResult processDevicesBulkImport(@RequestBody BulkImportRequest request) throws Exception {
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java b/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
index 63c151b24c..6802130338 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityViewController.java
+++ b/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.EntityView;
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.edge.Edge;
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.EntityViewId;
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.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
@@ -74,7 +72,6 @@ import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
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;
/**
diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java
index f8ac4acf49..3459d61c8b 100644
--- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java
+++ b/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)
@ResponseBody
public Tenant getTenantById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
- checkParameter("tenantId", strTenantId);
+ checkParameter(TENANT_ID, strTenantId);
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
Tenant tenant = checkTenantId(tenantId, Operation.READ);
@@ -74,7 +74,7 @@ public class TenantController extends BaseController {
@RequestMapping(value = "/tenant/info/{tenantId}", method = RequestMethod.GET)
@ResponseBody
public TenantInfo getTenantInfoById(@PathVariable("tenantId") String strTenantId) throws ThingsboardException {
- checkParameter("tenantId", strTenantId);
+ checkParameter(TENANT_ID, strTenantId);
try {
TenantId tenantId = new TenantId(toUUID(strTenantId));
return checkTenantInfoId(tenantId, Operation.READ);
diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
index 6677888854..0a82994a42 100644
--- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
+++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java
@@ -196,6 +196,13 @@ public class DefaultTbClusterService implements TbClusterService {
@Override
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);
log.trace("PUSHING msg: {} to:{}", response, tpi);
producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), response), callback);
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
index eca8864fda..e0d2cca803 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java
@@ -87,6 +87,10 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi
@Override
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()
.setRequestId(requestId)
.setPayload(body).build();
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java
new file mode 100644
index 0000000000..97bf1d8b2d
--- /dev/null
+++ b/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;
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java
new file mode 100644
index 0000000000..7875692fc4
--- /dev/null
+++ b/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;
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java b/application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java
new file mode 100644
index 0000000000..e3f0b7d573
--- /dev/null
+++ b/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;
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java
new file mode 100644
index 0000000000..c668dab7ae
--- /dev/null
+++ b/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;
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java b/application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java
new file mode 100644
index 0000000000..3d457a7937
--- /dev/null
+++ b/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;
+}
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java
index 95f7e72ad7..b4101edc8e 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTbCoreToTransportService.java
@@ -54,6 +54,13 @@ public class DefaultTbCoreToTransportService implements TbCoreToTransportService
@Override
public void process(String nodeId, ToTransportMsg msg, Runnable onSuccess, Consumer 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);
UUID sessionId = new UUID(msg.getSessionIdMSB(), msg.getSessionIdLSB());
log.trace("[{}][{}] Pushing session data to topic: {}", tpi.getFullTopicName(), sessionId, msg);
diff --git a/common/data/pom.xml b/common/data/pom.xml
index 3b9152fb47..1b5c78bcd6 100644
--- a/common/data/pom.xml
+++ b/common/data/pom.xml
@@ -96,6 +96,10 @@
org.apache.commons
commons-lang3
+
+ io.swagger
+ swagger-annotations
+
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java
index 356af5dab7..61328dd352 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/AdminSettings.java
@@ -15,11 +15,15 @@
*/
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 com.fasterxml.jackson.databind.JsonNode;
+import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.validation.NoXss;
+@ApiModel
public class AdminSettings extends BaseData {
private static final long serialVersionUID = -7670322981725511892L;
@@ -42,6 +46,19 @@ public class AdminSettings extends BaseData {
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() {
return key;
}
@@ -50,6 +67,7 @@ public class AdminSettings extends BaseData {
this.key = key;
}
+ @ApiModelProperty(position = 4, value = "JSON representation of the Administration Settings value")
public JsonNode getJsonValue() {
return jsonValue;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
index 7d8a3d5266..c9a95375ea 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
@@ -16,6 +16,7 @@
package org.thingsboard.server.common.data;
import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.id.DashboardId;
public class Dashboard extends DashboardInfo {
@@ -41,6 +42,10 @@ public class Dashboard extends DashboardInfo {
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() {
return configuration;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
index b42e0069df..556f13d18e 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/DashboardInfo.java
@@ -16,6 +16,8 @@
package org.thingsboard.server.common.data;
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.DashboardId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -25,6 +27,7 @@ import javax.validation.Valid;
import java.util.HashSet;
import java.util.Set;
+@ApiModel
public class DashboardInfo extends SearchTextBased implements HasName, HasTenantId {
private TenantId tenantId;
@@ -54,6 +57,22 @@ public class DashboardInfo extends SearchTextBased implements HasNa
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() {
return tenantId;
}
@@ -62,6 +81,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa
this.tenantId = tenantId;
}
+ @ApiModelProperty(position = 4, value = "Title of the dashboard.")
public String getTitle() {
return title;
}
@@ -70,6 +90,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa
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() {
return image;
}
@@ -78,6 +99,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa
this.image = image;
}
+ @ApiModelProperty(position = 5, value = "List of assigned customers with their info.", readOnly = true)
public Set getAssignedCustomers() {
return assignedCustomers;
}
@@ -86,6 +108,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa
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() {
return mobileHide;
}
@@ -94,6 +117,7 @@ public class DashboardInfo extends SearchTextBased implements HasNa
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() {
return mobileOrder;
}
@@ -152,6 +176,7 @@ public class DashboardInfo extends SearchTextBased 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
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java
index c5519d329b..27951402ce 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/Device.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/Device.java
@@ -17,6 +17,9 @@ package org.thingsboard.server.common.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
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.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.device.data.DeviceData;
@@ -31,6 +34,7 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Optional;
+@ApiModel
@EqualsAndHashCode(callSuper = true)
@Slf4j
public class Device extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId, HasOtaPackage {
@@ -88,6 +92,22 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
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() {
return tenantId;
}
@@ -96,6 +116,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
this.tenantId = tenantId;
}
+ @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignDeviceToCustomer' to change the Customer Id.", readOnly = true)
public CustomerId getCustomerId() {
return customerId;
}
@@ -104,6 +125,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
this.customerId = customerId;
}
+ @ApiModelProperty(position = 5, required = true, value = "Unique Device Name in scope of Tenant", example = "A4B72CCDFF33")
@Override
public String getName() {
return name;
@@ -113,6 +135,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
this.name = name;
}
+ @ApiModelProperty(position = 6, required = true, value = "Device Profile Name", example = "Temperature Sensor")
public String getType() {
return type;
}
@@ -121,6 +144,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
this.type = type;
}
+ @ApiModelProperty(position = 7, required = true, value = "Label that may be used in widgets", example = "Room 234 Sensor")
public String getLabel() {
return label;
}
@@ -129,6 +153,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
this.label = label;
}
+ @ApiModelProperty(position = 8, required = true, value = "JSON object with Device Profile Id.")
public DeviceProfileId getDeviceProfileId() {
return deviceProfileId;
}
@@ -137,6 +162,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
this.deviceProfileId = deviceProfileId;
}
+ @ApiModelProperty(position = 9, value = "JSON object with content specific to type of transport in the device profile.")
public DeviceData getDeviceData() {
if (deviceData != null) {
return deviceData;
@@ -169,6 +195,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
return getName();
}
+ @ApiModelProperty(position = 10, value = "JSON object with Ota Package Id.")
public OtaPackageId getFirmwareId() {
return firmwareId;
}
@@ -177,6 +204,7 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
this.firmwareId = firmwareId;
}
+ @ApiModelProperty(position = 11, value = "JSON object with Ota Package Id.")
public OtaPackageId getSoftwareId() {
return softwareId;
}
@@ -185,6 +213,12 @@ public class Device extends SearchTextBasedWithAdditionalInfo implemen
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
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java
index 7730ea635b..be872cc059 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceInfo.java
@@ -15,14 +15,20 @@
*/
package org.thingsboard.server.common.data;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.id.DeviceId;
+@ApiModel
@Data
public class DeviceInfo extends Device {
+ @ApiModelProperty(position = 13, value = "Title of the Customer that owns the device.", readOnly = true)
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;
+ @ApiModelProperty(position = 15, value = "Name of the corresponding Device Profile.", readOnly = true)
private String deviceProfileName;
public DeviceInfo() {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java
index f2985e3371..6202d7391f 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboard.java
@@ -15,11 +15,17 @@
*/
package org.thingsboard.server.common.data;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
+@ApiModel
@Data
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;
public HomeDashboard(Dashboard dashboard, boolean hideDashboardToolbar) {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java
index 1cbc1c737e..dc6cdb55ef 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/HomeDashboardInfo.java
@@ -15,13 +15,18 @@
*/
package org.thingsboard.server.common.data;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.id.DashboardId;
+@ApiModel
@Data
@AllArgsConstructor
public class HomeDashboardInfo {
+ @ApiModelProperty(position = 1, value = "JSON object with the dashboard Id.")
private DashboardId dashboardId;
+ @ApiModelProperty(position = 1, value = HomeDashboard.HIDE_DASHBOARD_TOOLBAR_DESCRIPTION)
private boolean hideDashboardToolbar;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java
index d73dee6e1b..785bc541fb 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/ShortCustomerInfo.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
@@ -25,16 +27,20 @@ import org.thingsboard.server.common.data.validation.NoXss;
* Created by igor on 2/27/18.
*/
+@ApiModel
@AllArgsConstructor
public class ShortCustomerInfo {
+ @ApiModelProperty(position = 1, value = "JSON object with the customer Id.")
@Getter @Setter
private CustomerId customerId;
+ @ApiModelProperty(position = 2, value = "Title of the customer.")
@Getter @Setter
@NoXss
private String title;
+ @ApiModelProperty(position = 3, value = "Indicates special 'Public' customer used to embed dashboards on public websites.")
@Getter @Setter
private boolean isPublic;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java b/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java
index dc88e6c295..78b08cd456 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/UpdateMessage.java
@@ -15,12 +15,17 @@
*/
package org.thingsboard.server.common.data;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
+@ApiModel
@Data
public class UpdateMessage {
+ @ApiModelProperty(position = 1, value = "The message about new platform update available.")
private final String message;
+ @ApiModelProperty(position = 1, value = "'True' if new platform update is available.")
private final boolean isUpdateAvailable;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/User.java b/common/data/src/main/java/org/thingsboard/server/common/data/User.java
index 420aff71ce..f6c4c88eec 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/User.java
+++ b/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.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.id.CustomerId;
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;
+@ApiModel
@EqualsAndHashCode(callSuper = true)
public class User extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId {
@@ -58,6 +62,23 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H
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() {
return tenantId;
}
@@ -66,6 +87,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H
this.tenantId = tenantId;
}
+ @ApiModelProperty(position = 4, value = "JSON object with the Customer Id.", readOnly = true)
public CustomerId getCustomerId() {
return customerId;
}
@@ -74,6 +96,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H
this.customerId = customerId;
}
+ @ApiModelProperty(position = 5, required = true, value = "Email of the user", example = "user@example.com")
public String getEmail() {
return email;
}
@@ -82,12 +105,14 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H
this.email = email;
}
+ @ApiModelProperty(position = 6, readOnly = true, value = "Duplicates the email of the user, readonly", example = "user@example.com")
@Override
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
public String getName() {
return email;
}
+ @ApiModelProperty(position = 7, required = true, value = "Authority", example = "SYS_ADMIN, TENANT_ADMIN or CUSTOMER_USER")
public Authority getAuthority() {
return authority;
}
@@ -96,6 +121,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H
this.authority = authority;
}
+ @ApiModelProperty(position = 8, required = true, value = "First name of the user", example = "John")
public String getFirstName() {
return firstName;
}
@@ -104,6 +130,7 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H
this.firstName = firstName;
}
+ @ApiModelProperty(position = 9, required = true, value = "Last name of the user", example = "Doe")
public String getLastName() {
return lastName;
}
@@ -112,6 +139,12 @@ public class User extends SearchTextBasedWithAdditionalInfo implements H
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
public String getSearchText() {
return getEmail();
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
index 594e6b635f..6caee2f111 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/Asset.java
@@ -15,6 +15,9 @@
*/
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 org.thingsboard.server.common.data.HasCustomerId;
import org.thingsboard.server.common.data.HasName;
@@ -27,6 +30,7 @@ import org.thingsboard.server.common.data.validation.NoXss;
import java.util.Optional;
+@ApiModel
@EqualsAndHashCode(callSuper = true)
public class Asset extends SearchTextBasedWithAdditionalInfo implements HasName, HasTenantId, HasCustomerId {
@@ -67,6 +71,22 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements
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() {
return tenantId;
}
@@ -75,6 +95,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements
this.tenantId = tenantId;
}
+ @ApiModelProperty(position = 4, value = "JSON object with Customer Id. Use 'assignAssetToCustomer' to change the Customer Id.", readOnly = true)
public CustomerId getCustomerId() {
return customerId;
}
@@ -83,6 +104,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements
this.customerId = customerId;
}
+ @ApiModelProperty(position = 5, required = true, value = "Unique Asset Name in scope of Tenant", example = "Empire State Building")
@Override
public String getName() {
return name;
@@ -92,6 +114,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements
this.name = name;
}
+ @ApiModelProperty(position = 6, required = true, value = "Asset type", example = "Building")
public String getType() {
return type;
}
@@ -100,6 +123,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements
this.type = type;
}
+ @ApiModelProperty(position = 7, required = true, value = "Label that may be used in widgets", example = "NY Building")
public String getLabel() {
return label;
}
@@ -113,6 +137,12 @@ public class Asset extends SearchTextBasedWithAdditionalInfo implements
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
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
index ad3de54fc4..59d451bac6 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetInfo.java
@@ -15,13 +15,18 @@
*/
package org.thingsboard.server.common.data.asset;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.id.AssetId;
+@ApiModel
@Data
public class AssetInfo extends Asset {
+ @ApiModelProperty(position = 9, value = "Title of the Customer that owns the asset.", readOnly = true)
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;
public AssetInfo() {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java
index fe916f0eaa..d3fcad8e93 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/AssetSearchQuery.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.common.data.asset;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.relation.EntityRelation;
@@ -31,8 +32,11 @@ import java.util.List;
@Data
public class AssetSearchQuery {
+ @ApiModelProperty(position = 3, value = "Main search 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;
+ @ApiModelProperty(position = 2, value = "Array of asset types to filter the related entities (e.g. 'Building', 'Vehicle').")
private List assetTypes;
public EntityRelationsQuery toEntitySearchQuery() {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java
index 9143fdfece..6449cf6729 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/DeviceSearchQuery.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.device;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
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.List;
+@ApiModel
@Data
public class DeviceSearchQuery {
+ @ApiModelProperty(position = 3, value = "Main search 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;
+ @ApiModelProperty(position = 2, value = "Array of device types to filter the related entities (e.g. 'Temperature Sensor', 'Smoke Sensor').")
private List deviceTypes;
public EntityRelationsQuery toEntitySearchQuery() {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java
index ffae06336b..1ad613f105 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DefaultDeviceConfiguration.java
+++ b/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;
+import io.swagger.annotations.ApiModel;
import lombok.Data;
import org.thingsboard.server.common.data.DeviceProfileType;
+@ApiModel
@Data
public class DefaultDeviceConfiguration implements DeviceConfiguration {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java
index 3baadc599e..16f751a136 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceConfiguration.java
+++ b/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.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import io.swagger.annotations.ApiModel;
import org.thingsboard.server.common.data.DeviceProfileType;
+@ApiModel
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java
index f212d4a9c8..06e52d47a0 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceData.java
+++ b/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;
+import io.swagger.annotations.ApiModel;
import lombok.Data;
+@ApiModel
@Data
public class DeviceData {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java
index e5c2ce89d2..e39ed8ac15 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/DeviceTransportConfiguration.java
@@ -19,10 +19,12 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import io.swagger.annotations.ApiModel;
import org.thingsboard.server.common.data.DeviceTransportType;
import java.io.Serializable;
+@ApiModel
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java
index 251a8d5808..3211a88514 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AlarmId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
+@ApiModel
public class AlarmId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@@ -35,7 +38,7 @@ public class AlarmId extends UUIDBased implements EntityId {
return new AlarmId(UUID.fromString(alarmId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "ALARM", allowableValues = "ALARM")
@Override
public EntityType getEntityType() {
return EntityType.ALARM;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java
index 83dda3069a..1ebcbf681e 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
+@ApiModel
public class ApiUsageStateId extends UUIDBased implements EntityId {
@JsonCreator
@@ -33,7 +36,7 @@ public class ApiUsageStateId extends UUIDBased implements EntityId {
return new ApiUsageStateId(UUID.fromString(userId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "API_USAGE_STATE", allowableValues = "API_USAGE_STATE")
@Override
public EntityType getEntityType() {
return EntityType.API_USAGE_STATE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java
index 881e9c24a2..3361266905 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/AssetId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
+@ApiModel
public class AssetId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@@ -35,7 +38,7 @@ public class AssetId extends UUIDBased implements EntityId {
return new AssetId(UUID.fromString(assetId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "ASSET", allowableValues = "ASSET")
@Override
public EntityType getEntityType() {
return EntityType.ASSET;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java
index 344449ecab..d40bf20655 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/CustomerId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
+@ApiModel
public final class CustomerId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@@ -31,7 +34,7 @@ public final class CustomerId extends UUIDBased implements EntityId {
super(id);
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "CUSTOMER", allowableValues = "CUSTOMER")
@Override
public EntityType getEntityType() {
return EntityType.CUSTOMER;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java
index 93ce73c3ca..ff8b7f8584 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DashboardId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
+@ApiModel
public class DashboardId extends UUIDBased implements EntityId {
@JsonCreator
@@ -33,7 +36,7 @@ public class DashboardId extends UUIDBased implements EntityId {
return new DashboardId(UUID.fromString(dashboardId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "DASHBOARD", allowableValues = "DASHBOARD")
@Override
public EntityType getEntityType() {
return EntityType.DASHBOARD;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java
index 3aed66733c..f2df1471c9 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
+@ApiModel
public class DeviceId extends UUIDBased implements EntityId {
private static final long serialVersionUID = 1L;
@@ -35,8 +38,8 @@ public class DeviceId extends UUIDBased implements EntityId {
return new DeviceId(UUID.fromString(deviceId));
}
- @JsonIgnore
@Override
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "DEVICE", allowableValues = "DEVICE")
public EntityType getEntityType() {
return EntityType.DEVICE;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java
index e3c830409e..18e428257c 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/DeviceProfileId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
public class DeviceProfileId extends UUIDBased implements EntityId {
@@ -35,7 +36,7 @@ public class DeviceProfileId extends UUIDBased implements EntityId {
return new DeviceProfileId(UUID.fromString(deviceProfileId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "DEVICE_PROFILE", allowableValues = "DEVICE_PROFILE")
@Override
public EntityType getEntityType() {
return EntityType.DEVICE_PROFILE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java
index 92421c39fe..0a1495a14c 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EdgeId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@@ -35,7 +36,7 @@ public class EdgeId extends UUIDBased implements EntityId {
return new EdgeId(UUID.fromString(integrationId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "EDGE", allowableValues = "EDGE")
@Override
public EntityType getEntityType() {
return EntityType.EDGE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java
index 26403228a3..599bd78473 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityViewId.java
+++ b/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.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@@ -37,6 +38,7 @@ public class EntityViewId extends UUIDBased implements EntityId {
return new EntityViewId(UUID.fromString(entityViewID));
}
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "ENTITY_VIEW", allowableValues = "ENTITY_VIEW")
@Override
public EntityType getEntityType() {
return EntityType.ENTITY_VIEW;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java
index 0442792238..3d439fa8ed 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/OtaPackageId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@@ -35,7 +36,7 @@ public class OtaPackageId extends UUIDBased implements EntityId {
return new OtaPackageId(UUID.fromString(firmwareId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "OTA_PACKAGE", allowableValues = "OTA_PACKAGE")
@Override
public EntityType getEntityType() {
return EntityType.OTA_PACKAGE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java
index a4cd8797b0..4e7782f6a6 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/RpcId.java
@@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@@ -31,7 +32,7 @@ public final class RpcId extends UUIDBased implements EntityId {
super(id);
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "RPC", allowableValues = "RPC")
@Override
public EntityType getEntityType() {
return EntityType.RPC;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java
index fc5aba9430..04a5a94608 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleChainId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@@ -29,7 +30,7 @@ public class RuleChainId extends UUIDBased implements EntityId {
super(id);
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "RULE_CHAIN", allowableValues = "RULE_CHAIN")
@Override
public EntityType getEntityType() {
return EntityType.RULE_CHAIN;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java
index 9b2da26d90..e448243dcb 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/RuleNodeId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@@ -29,7 +30,7 @@ public class RuleNodeId extends UUIDBased implements EntityId {
super(id);
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "RULE_NODE", allowableValues = "RULE_NODE")
@Override
public EntityType getEntityType() {
return EntityType.RULE_NODE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java
index 566b62db66..66b5d0f194 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TbResourceId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
import java.util.UUID;
@@ -31,7 +32,7 @@ public class TbResourceId extends UUIDBased implements EntityId {
super(id);
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "TB_RESOURCE", allowableValues = "TB_RESOURCE")
@Override
public EntityType getEntityType() {
return EntityType.TB_RESOURCE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java
index b5c559b9b8..a785ac03d5 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
public final class TenantId extends UUIDBased implements EntityId {
@@ -34,7 +35,7 @@ public final class TenantId extends UUIDBased implements EntityId {
super(id);
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "TENANT", allowableValues = "TENANT")
@Override
public EntityType getEntityType() {
return EntityType.TENANT;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java
index 70bce39a78..4c76aa707b 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/TenantProfileId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
public class TenantProfileId extends UUIDBased implements EntityId {
@@ -35,7 +36,7 @@ public class TenantProfileId extends UUIDBased implements EntityId {
return new TenantProfileId(UUID.fromString(tenantProfileId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "TENANT_PROFILE", allowableValues = "TENANT_PROFILE")
@Override
public EntityType getEntityType() {
return EntityType.TENANT_PROFILE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java
index a632e759e9..72a8636374 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UUIDBased.java
@@ -15,9 +15,13 @@
*/
package org.thingsboard.server.common.data.id;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+
import java.io.Serializable;
import java.util.UUID;
+@ApiModel
public abstract class UUIDBased implements HasUUID, Serializable {
private static final long serialVersionUID = 1L;
@@ -33,6 +37,7 @@ public abstract class UUIDBased implements HasUUID, Serializable {
this.id = id;
}
+ @ApiModelProperty(position = 1, required = true, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9")
public UUID getId() {
return id;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java
index cab6a16242..aa4ca4da79 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/UserId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
public class UserId extends UUIDBased implements EntityId {
@@ -33,7 +34,7 @@ public class UserId extends UUIDBased implements EntityId {
return new UserId(UUID.fromString(userId));
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "USER", allowableValues = "USER")
@Override
public EntityType getEntityType() {
return EntityType.USER;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java
index 71591076d3..c75a99345f 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetTypeId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
public final class WidgetTypeId extends UUIDBased implements EntityId {
@@ -31,7 +32,7 @@ public final class WidgetTypeId extends UUIDBased implements EntityId {
super(id);
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "WIDGET_TYPE", allowableValues = "WIDGET_TYPE")
@Override
public EntityType getEntityType() {
return EntityType.WIDGET_TYPE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java
index 56ab09556c..2ad772a898 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/WidgetsBundleId.java
+++ b/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.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
+import io.swagger.annotations.ApiModelProperty;
import org.thingsboard.server.common.data.EntityType;
public final class WidgetsBundleId extends UUIDBased implements EntityId {
@@ -31,7 +32,7 @@ public final class WidgetsBundleId extends UUIDBased implements EntityId {
super(id);
}
- @JsonIgnore
+ @ApiModelProperty(position = 2, required = true, value = "string", example = "WIDGETS_BUNDLE", allowableValues = "WIDGETS_BUNDLE")
@Override
public EntityType getEntityType() {
return EntityType.WIDGETS_BUNDLE;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java
index 6ffbce4d3d..eb1d4ceca8 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java
+++ b/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.JsonProperty;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import java.util.Collections;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
+@ApiModel
public class PageData {
private final List data;
@@ -45,18 +48,22 @@ public class PageData {
this.hasNext = hasNext;
}
+ @ApiModelProperty(position = 1, value = "Array of the entities", readOnly = true)
public List getData() {
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() {
return totalPages;
}
+ @ApiModelProperty(position = 3, value = "Total number of elements in all available pages", readOnly = true)
public long getTotalElements() {
return totalElements;
}
+ @ApiModelProperty(position = 4, value = "'false' value indicates the end of the result set", readOnly = true)
@JsonProperty("hasNext")
public boolean hasNext() {
return hasNext;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java
index 1a6eb48eac..5c856cf38d 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentDescriptor.java
@@ -16,6 +16,8 @@
package org.thingsboard.server.common.data.plugin;
import com.fasterxml.jackson.databind.JsonNode;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import org.thingsboard.server.common.data.SearchTextBased;
import org.thingsboard.server.common.data.id.ComponentDescriptorId;
@@ -23,16 +25,23 @@ import org.thingsboard.server.common.data.id.ComponentDescriptorId;
/**
* @author Andrew Shvayka
*/
+@ApiModel
@ToString
public class ComponentDescriptor extends SearchTextBased {
private static final long serialVersionUID = 1L;
+ @ApiModelProperty(position = 3, value = "Type of the Rule Node", readOnly = true)
@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;
+ @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;
+ @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;
+ @ApiModelProperty(position = 7, value = "Complex JSON object that represents the Rule Node configuration.", readOnly = true)
@Getter @Setter private transient JsonNode configurationDescriptor;
+ @ApiModelProperty(position = 8, value = "Rule Node Actions. Deprecated. Always null.", readOnly = true)
@Getter @Setter private String actions;
public ComponentDescriptor() {
@@ -53,12 +62,26 @@ public class ComponentDescriptor extends SearchTextBased
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
public String getSearchText() {
return name;
}
-
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -84,4 +107,5 @@ public class ComponentDescriptor extends SearchTextBased
result = 31 * result + (actions != null ? actions.hashCode() : 0);
return result;
}
+
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java
index f44337ff29..f195840f3b 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java
@@ -15,6 +15,8 @@
*/
package org.thingsboard.server.common.data.relation;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.thingsboard.server.common.data.EntityType;
@@ -26,15 +28,22 @@ import java.util.UUID;
/**
* Created by ashvayka on 03.05.17.
*/
+@ApiModel
@Data
@AllArgsConstructor
public class RelationsSearchParameters {
+ @ApiModelProperty(position = 1, value = "Root entity id to start search from.")
private UUID rootId;
+ @ApiModelProperty(position = 2, value = "Type of the root entity.")
private EntityType rootType;
+ @ApiModelProperty(position = 3, value = "Type of the root entity.")
private EntitySearchDirection direction;
+ @ApiModelProperty(position = 4, value = "Type of the relation.")
private RelationTypeGroup relationTypeGroup;
+ @ApiModelProperty(position = 5, value = "Maximum level of the search depth.")
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;
public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel, boolean fetchLastLevelOnly) {
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java
index 337e6cf349..b277178503 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java
@@ -15,16 +15,18 @@
*/
package org.thingsboard.server.common.data.security;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.id.DeviceCredentialsId;
import org.thingsboard.server.common.data.id.DeviceId;
+@ApiModel
@EqualsAndHashCode(callSuper = true)
public class DeviceCredentials extends BaseData implements DeviceCredentialsFilter {
private static final long serialVersionUID = -7869261127032877765L;
-
private DeviceId deviceId;
private DeviceCredentialsType credentialsType;
private String credentialsId;
@@ -46,6 +48,21 @@ public class DeviceCredentials extends BaseData implements
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() {
return deviceId;
}
@@ -54,6 +71,7 @@ public class DeviceCredentials extends BaseData implements
this.deviceId = deviceId;
}
+ @ApiModelProperty(position = 4, value = "Type of the credentials", allowableValues="ACCESS_TOKEN, X509_CERTIFICATE, MQTT_BASIC, LWM2M_CREDENTIALS")
@Override
public DeviceCredentialsType getCredentialsType() {
return credentialsType;
@@ -63,6 +81,11 @@ public class DeviceCredentials extends BaseData implements
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
public String getCredentialsId() {
return credentialsId;
@@ -72,6 +95,9 @@ public class DeviceCredentials extends BaseData implements
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() {
return credentialsValue;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java
index 5674df0ef1..5c1972d1e1 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/SecuritySettings.java
+++ b/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;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
+@ApiModel
@Data
public class SecuritySettings implements Serializable {
+ @ApiModelProperty(position = 1, value = "The user password policy object." )
private UserPasswordPolicy passwordPolicy;
-
+ @ApiModelProperty(position = 2, value = "Maximum number of failed login attempts allowed before user account is locked." )
private Integer maxFailedLoginAttempts;
+ @ApiModelProperty(position = 3, value = "Email to use for notifications about locked users." )
private String userLockoutNotificationEmail;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java
index f719316b37..28bdae82c3 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/security/model/UserPasswordPolicy.java
+++ b/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;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
+@ApiModel
@Data
public class UserPasswordPolicy implements Serializable {
+ @ApiModelProperty(position = 1, value = "Minimum number of symbols in the password." )
private Integer minimumLength;
+ @ApiModelProperty(position = 1, value = "Minimum number of uppercase letters in the password." )
private Integer minimumUppercaseLetters;
+ @ApiModelProperty(position = 1, value = "Minimum number of lowercase letters in the password." )
private Integer minimumLowercaseLetters;
+ @ApiModelProperty(position = 1, value = "Minimum number of digits in the password." )
private Integer minimumDigits;
+ @ApiModelProperty(position = 1, value = "Minimum number of special in the password." )
private Integer minimumSpecialCharacters;
+ @ApiModelProperty(position = 1, value = "Password expiration period (days). Force expiration of the password." )
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;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java
index e5fd92b38d..08a3211c0a 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/AwsSnsSmsProviderConfiguration.java
+++ b/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;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
+@ApiModel
@Data
public class AwsSnsSmsProviderConfiguration implements SmsProviderConfiguration {
+ @ApiModelProperty(position = 1, value = "The AWS SNS Access Key ID.")
private String accessKeyId;
+ @ApiModelProperty(position = 2, value = "The AWS SNS Access Key.")
private String secretAccessKey;
+ @ApiModelProperty(position = 3, value = "The AWS region.")
private String region;
@Override
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java
index 4e6d17ba9c..843d116cee 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TestSmsRequest.java
+++ b/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;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
+@ApiModel
@Data
public class TestSmsRequest {
+ @ApiModelProperty(position = 1, value = "The SMS provider configuration")
private SmsProviderConfiguration providerConfiguration;
+ @ApiModelProperty(position = 2, value = "The phone number or other identifier to specify as a recipient of the SMS.")
private String numberTo;
+ @ApiModelProperty(position = 3, value = "The test message")
private String message;
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java
index 800176472b..d6191fc9a5 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/sms/config/TwilioSmsProviderConfiguration.java
+++ b/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;
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
+@ApiModel
@Data
public class TwilioSmsProviderConfiguration implements SmsProviderConfiguration {
+ @ApiModelProperty(position = 1, value = "Twilio account Sid.")
private String accountSid;
+ @ApiModelProperty(position = 2, value = "Twilio account Token.")
private String accountToken;
+ @ApiModelProperty(position = 3, value = "The number/id of a sender.")
private String numberFrom;
@Override
diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java
new file mode 100644
index 0000000000..a53124860c
--- /dev/null
+++ b/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());
+ }
+
+}
\ No newline at end of file
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
index 1207f4d634..83715994da 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
@@ -136,8 +136,9 @@ public final class TbMsg implements Serializable {
tbMsg.data, ruleChainId, null, tbMsg.ruleNodeExecCounter.get(), tbMsg.getCallback());
}
- public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
- return new TbMsg(tbMsg.getQueueName(), UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.customerId, tbMsg.getMetaData().copy(),
+ //used for enqueueForTellNext
+ 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);
}
diff --git a/dao/pom.xml b/dao/pom.xml
index 922b389bff..f490a0e8e5 100644
--- a/dao/pom.xml
+++ b/dao/pom.xml
@@ -201,6 +201,11 @@
org.springframework.boot
spring-boot-starter-data-jpa
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
org.springframework
spring-test
@@ -211,6 +216,16 @@
hsqldb
test
+
+ org.testcontainers
+ postgresql
+ test
+
+
+ org.testcontainers
+ jdbc
+ test
+
org.springframework
spring-context-support
@@ -239,7 +254,14 @@
maven-surefire-plugin
${surfire.version}
+
+ **/sql/*Test.java
+ **/sql/*/*Test.java
+ **/psql/*Test.java
+ **/nosql/*Test.java
+
+ **/*Test.java
**/*TestSuite.java
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
index 7b3ea36cfa..f567420947 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java
@@ -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, " +
"coalesce((select title from tenant where id = aus.entity_id), (select title from customer where id = aus.entity_id)) as name " +
"from api_usage_state as aus)";
+ static final int MAX_LEVEL_DEFAULT = 50; //This value has to be reasonable small to prevent infinite recursion as early as possible
static {
entityTableMap.put(EntityType.ASSET, "asset");
@@ -239,18 +240,30 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{
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 (" +
- " SELECT from_id, from_type, to_id, to_type, relation_type, 1 as lvl" +
- " FROM relation" +
+ 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," +
+ " 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'" +
+ " GROUP BY from_id, from_type, to_id, to_type, lvl, path" +
" 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" +
" INNER JOIN related_entities re ON" +
" r.$in_id = re.$out_id and r.$in_type = re.$out_type and" +
- " relation_type_group = 'COMMON' %s)" +
- " SELECT re.$out_id entity_id, re.$out_type entity_type, max(re.lvl) lvl" +
- " from related_entities re" +
+ " relation_type_group = 'COMMON' " +
+ " AND r.$in_id NOT IN (SELECT * FROM unnest(re.path)) " +
+ " %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";
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");
@@ -580,7 +593,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
.append("nr.").append(fromOrTo).append("_type").append(" = re.").append(toOrFrom).append("_type");
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);
String query = "( " + selectFields + from + ")";
@@ -659,7 +672,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
.append(whereFilter.toString().replaceAll("re\\.", "nr\\."));
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);
return "( " + selectFields + from + ")";
@@ -693,8 +706,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository {
return whereFilter.toString();
}
- private String getLvlFilter(int maxLevel) {
- return maxLevel > 0 ? ("and lvl <= " + (maxLevel - 1)) : "";
+ String getLvlFilter(int maxLevel) {
+ return "and re.lvl <= " + (getMaxLevel(maxLevel) - 1);
+ }
+
+ int getMaxLevel(int maxLevel) {
+ return maxLevel > 0 ? maxLevel : MAX_LEVEL_DEFAULT;
}
private String getQueryTemplate(EntitySearchDirection direction) {
diff --git a/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java
new file mode 100644
index 0000000000..b89c70e8f4
--- /dev/null
+++ b/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 {
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java
new file mode 100644
index 0000000000..429fa1d711
--- /dev/null
+++ b/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 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);
+ }
+ }
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
index 2a1ff95062..cfd7923969 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseDeviceProfileServiceTest.java
@@ -47,7 +47,7 @@ import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE;
-public class BaseDeviceProfileServiceTest extends AbstractServiceTest {
+public abstract class BaseDeviceProfileServiceTest extends AbstractServiceTest {
private IdComparator idComparator = new IdComparator<>();
private IdComparator deviceProfileInfoIdComparator = new IdComparator<>();
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
index 831d92965b..e58191b60d 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java
+++ b/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.ListenableFuture;
+import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.RandomUtils;
+import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.ResultSetExtractor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
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.dao.attributes.AttributesService;
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 java.util.ArrayList;
@@ -82,9 +86,13 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+@Slf4j
public abstract class BaseEntityServiceTest extends AbstractServiceTest {
+ static final int ENTITY_COUNT = 5;
+
@Autowired
private AttributesService attributesService;
@@ -96,6 +104,9 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
@Autowired
private JdbcTemplate template;
+ @Autowired
+ private RelationRepository relationRepository;
+
@Before
public void before() {
Tenant tenant = new Tenant();
@@ -110,7 +121,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
tenantService.deleteTenant(tenantId);
}
-
+
@Test
public void testCountEntitiesByQuery() throws InterruptedException {
List devices = new ArrayList<>();
@@ -154,12 +165,12 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
Assert.assertEquals(0, count);
}
-
+
@Test
public void testCountHierarchicalEntitiesByQuery() throws InterruptedException {
List assets = new ArrayList<>();
List 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();
filter.setRootEntity(tenantId);
@@ -168,7 +179,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
EntityCountQuery countQuery = new EntityCountQuery(filter);
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))));
count = entityService.countEntitiesByQuery(tenantId, new CustomerId(CustomerId.NULL_UUID), countQuery);
@@ -304,11 +315,25 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
@Test
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 assets = new ArrayList<>();
List devices = new ArrayList<>();
List temperatures = new ArrayList<>();
List highTemperatures = new ArrayList<>();
- createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
+ createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
List>> attributeFutures = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) {
@@ -321,6 +346,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
filter.setRootEntity(tenantId);
filter.setDirection(EntitySearchDirection.FROM);
filter.setFilters(Collections.singletonList(new RelationEntityTypeFilter("Contains", Collections.singletonList(EntityType.DEVICE))));
+ filter.setMaxLevel(maxLevel);
+ filter.setFetchLastLevelOnly(fetchLastLevelOnly);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
@@ -373,14 +400,13 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId);
}
-
@Test
public void testHierarchicalFindDevicesWithAttributesByQuery() throws ExecutionException, InterruptedException {
List assets = new ArrayList<>();
List devices = new ArrayList<>();
List temperatures = new ArrayList<>();
List highTemperatures = new ArrayList<>();
- createTestHierarchy(assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
+ createTestHierarchy(tenantId, assets, devices, new ArrayList<>(), new ArrayList<>(), temperatures, highTemperatures);
List>> attributeFutures = new ArrayList<>();
for (int i = 0; i < devices.size(); i++) {
@@ -393,6 +419,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
filter.setRootEntity(tenantId);
filter.setDirection(EntitySearchDirection.FROM);
filter.setRelationType("Contains");
+ filter.setMaxLevel(2);
+ filter.setFetchLastLevelOnly(true);
EntityDataSortOrder sortOrder = new EntityDataSortOrder(
new EntityKey(EntityKeyType.ENTITY_FIELD, "createdTime"), EntityDataSortOrder.Direction.ASC
@@ -446,14 +474,14 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId);
}
-
+
@Test
public void testHierarchicalFindAssetsWithAttributesByQuery() throws ExecutionException, InterruptedException {
List assets = new ArrayList<>();
List devices = new ArrayList<>();
List consumptions = new ArrayList<>();
List highConsumptions = new ArrayList<>();
- createTestHierarchy(assets, devices, consumptions, highConsumptions, new ArrayList<>(), new ArrayList<>());
+ createTestHierarchy(tenantId, assets, devices, consumptions, highConsumptions, new ArrayList<>(), new ArrayList<>());
List>> attributeFutures = new ArrayList<>();
for (int i = 0; i < assets.size(); i++) {
@@ -518,8 +546,8 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId);
}
- private void createTestHierarchy(List assets, List devices, List consumptions, List highConsumptions, List temperatures, List highTemperatures) throws InterruptedException {
- for (int i = 0; i < 5; i++) {
+ private void createTestHierarchy(TenantId tenantId, List assets, List devices, List consumptions, List highConsumptions, List temperatures, List highTemperatures) throws InterruptedException {
+ for (int i = 0; i < ENTITY_COUNT; i++) {
Asset asset = new Asset();
asset.setTenantId(tenantId);
asset.setName("Asset" + i);
@@ -529,18 +557,19 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
//TO make sure devices have different created time
Thread.sleep(1);
assets.add(asset);
- EntityRelation er = new EntityRelation();
- er.setFrom(tenantId);
- er.setTo(asset.getId());
- er.setType("Manages");
- er.setTypeGroup(RelationTypeGroup.COMMON);
- relationService.saveRelation(tenantId, er);
+ createRelation(tenantId, "Manages", tenantId, asset.getId());
long consumption = (long) (Math.random() * 100);
consumptions.add(consumption);
if (consumption > 50) {
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.setTenantId(tenantId);
device.setName("A" + i + "Device" + j);
@@ -550,22 +579,125 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
//TO make sure devices have different created time
Thread.sleep(1);
devices.add(device);
- er = new EntityRelation();
- er.setFrom(asset.getId());
- er.setTo(device.getId());
- er.setType("Contains");
- er.setTypeGroup(RelationTypeGroup.COMMON);
- relationService.saveRelation(tenantId, er);
+ createRelation(tenantId, "Contains", asset.getId(), device.getId());
long temperature = (long) (Math.random() * 100);
temperatures.add(temperature);
if (temperature > 45) {
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>> getListResultSetExtractor() {
+ return rs -> {
+ List> list = new ArrayList<>();
+ final int columnCount = rs.getMetaData().getColumnCount();
+ List columns = new ArrayList<>(columnCount);
+ for (int i = 1; i <= columnCount; i++) {
+ columns.add(rs.getMetaData().getColumnName(i));
+ }
+ list.add(columns);
+ while (rs.next()) {
+ List 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 assets) {
+ createLoopRelations(tenantId, type, assets.stream().map(Asset::getId).toArray(EntityId[]::new));
+ }
+
+ void createManyCustomRelationsBetweenTwoNodes(TenantId tenantId, String type, List assets, List 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 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
public void testSimpleFindEntityDataByQuery() throws InterruptedException {
List devices = new ArrayList<>();
@@ -871,7 +1003,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
}
@Test
- public void testBuildNumericPredicateQueryOperations() throws ExecutionException, InterruptedException{
+ public void testBuildNumericPredicateQueryOperations() throws ExecutionException, InterruptedException {
List devices = new ArrayList<>();
List temperatures = new ArrayList<>();
@@ -1031,7 +1163,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
deviceService.deleteDevicesByTenantId(tenantId);
}
-
+
@Test
public void testFindEntityDataByQueryWithTimeseries() throws ExecutionException, InterruptedException {
@@ -1122,7 +1254,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
}
@Test
- public void testBuildStringPredicateQueryOperations() throws ExecutionException, InterruptedException{
+ public void testBuildStringPredicateQueryOperations() throws ExecutionException, InterruptedException {
List devices = new ArrayList<>();
List attributeStrings = new ArrayList<>();
@@ -1142,11 +1274,11 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
devices.add(deviceService.saveDevice(device));
//TO make sure devices have different created time
Thread.sleep(1);
- List operationValues= Arrays.asList(StringFilterPredicate.StringOperation.values());
+ List operationValues = Arrays.asList(StringFilterPredicate.StringOperation.values());
StringFilterPredicate.StringOperation operation = operationValues.get(new Random().nextInt(operationValues.size()));
String operationName = operation.name();
attributeStrings.add(operationName);
- switch(operation){
+ switch (operation) {
case EQUAL:
equalStrings.add(operationName);
notContainsStrings.add(operationName);
@@ -1302,7 +1434,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
}
@Test
- public void testBuildStringPredicateQueryOperationsForEntityType() throws ExecutionException, InterruptedException{
+ public void testBuildStringPredicateQueryOperationsForEntityType() throws ExecutionException, InterruptedException {
List devices = new ArrayList<>();
@@ -1419,7 +1551,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
}
@Test
- public void testBuildSimplePredicateQueryOperations() throws InterruptedException{
+ public void testBuildSimplePredicateQueryOperations() throws InterruptedException {
List devices = new ArrayList<>();
@@ -1492,7 +1624,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
return loadedEntities;
}
- private List createStringKeyFilters(String key, EntityKeyType keyType, StringFilterPredicate.StringOperation operation, String value){
+ private List createStringKeyFilters(String key, EntityKeyType keyType, StringFilterPredicate.StringOperation operation, String value) {
KeyFilter filter = new KeyFilter();
filter.setKey(new EntityKey(keyType, key));
StringFilterPredicate predicate = new StringFilterPredicate();
@@ -1503,7 +1635,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest {
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();
filter.setKey(new EntityKey(keyType, key));
NumericFilterPredicate predicate = new NumericFilterPredicate();
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java
index c3073434e0..743591863a 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java
+++ b/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.UUID;
-public class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
+public abstract class BaseOAuth2ConfigTemplateServiceTest extends AbstractServiceTest {
@Autowired
protected OAuth2ConfigTemplateService oAuth2ConfigTemplateService;
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java
index 4077bb2176..0ad3470c99 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseOAuth2ServiceTest.java
+++ b/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.stream.Collectors;
-public class BaseOAuth2ServiceTest extends AbstractServiceTest {
+public abstract class BaseOAuth2ServiceTest extends AbstractServiceTest {
private static final OAuth2Info EMPTY_PARAMS = new OAuth2Info(false, Collections.emptyList());
@Autowired
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java
index 2ebb955c61..d77143f0a0 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java
+++ b/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.stream.Collectors;
-public class BaseTenantProfileServiceTest extends AbstractServiceTest {
+public abstract class BaseTenantProfileServiceTest extends AbstractServiceTest {
private IdComparator idComparator = new IdComparator<>();
private IdComparator tenantProfileInfoIdComparator = new IdComparator<>();
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java
new file mode 100644
index 0000000000..43ed1f4d02
--- /dev/null
+++ b/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 {
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/psql/EntityServicePostgreSqlTest.java
similarity index 77%
rename from dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceSqlTest.java
rename to dao/src/test/java/org/thingsboard/server/dao/service/psql/EntityServicePostgreSqlTest.java
index 0c59ae49c8..5b8c6d4aca 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/sql/EntityServiceSqlTest.java
+++ b/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
* 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.DaoSqlTest;
+import org.thingsboard.server.dao.service.DaoPostgreSqlTest;
-@DaoSqlTest
-public class EntityServiceSqlTest extends BaseEntityServiceTest {
+@DaoPostgreSqlTest
+public class EntityServicePostgreSqlTest extends BaseEntityServiceTest {
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepositoryTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepositoryTest.java
new file mode 100644
index 0000000000..a0c716d51f
--- /dev/null
+++ b/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));
+ }
+
+}
diff --git a/dao/src/test/resources/psql-test.properties b/dao/src/test/resources/psql-test.properties
new file mode 100644
index 0000000000..fb65966acf
--- /dev/null
+++ b/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
diff --git a/dao/src/test/resources/sql/system-test-psql.sql b/dao/src/test/resources/sql/system-test-psql.sql
new file mode 100644
index 0000000000..16dcb8c3ae
--- /dev/null
+++ b/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;
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index fa33548a56..0ca42294fa 100755
--- a/pom.xml
+++ b/pom.xml
@@ -85,6 +85,7 @@
3.0.2
2.6.1
1.0.0
+ 1.5.10
0.7
1.15.0
1.67
@@ -1623,6 +1624,11 @@
springfox-swagger2
${springfox-swagger.version}
+
+ io.swagger
+ swagger-annotations
+ ${swagger-annotations.version}
+
org.bouncycastle
bcprov-jdk15on
@@ -1637,6 +1643,17 @@
org.hsqldb
hsqldb
${hsqldb.version}
+
+
+ org.testcontainers
+ postgresql
+ ${testcontainers.version}
+ test
+
+
+ org.testcontainers
+ jdbc
+ ${testcontainers.version}
test
diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java
index f44c2a48d6..a50b4bbc26 100644
--- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java
+++ b/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 String METADATA_VARIABLE_TEMPLATE = "${%s}";
-
private static final Pattern DATA_PATTERN = Pattern.compile("(\\$\\[)(.*?)(])");
- private static final String DATA_VARIABLE_TEMPLATE = "$[%s]";
-
public static T convert(TbNodeConfiguration configuration, Class clazz) throws TbNodeException {
try {
return mapper.treeToValue(configuration.getData(), clazz);
@@ -80,7 +76,7 @@ public class TbNodeUtils {
}
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) {
- String varPattern = String.format(METADATA_VARIABLE_TEMPLATE, key);
- return pattern.replace(varPattern, val);
+ return pattern.replace(formatMetadataVarTemplate(key), val);
}
+ static String formatDataVarTemplate(String key) {
+ return "$[" + key + ']';
+ }
+
+ static String formatMetadataVarTemplate(String key) {
+ return "${" + key + '}';
+ }
}
diff --git a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java
index 11687e0415..30e9eb12b5 100644
--- a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java
+++ b/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;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Test;
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.common.util.JacksonUtil;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
@RunWith(MockitoJUnitRunner.class)
public class TbNodeUtilsTest {
+ private static final String DATA_VARIABLE_TEMPLATE = "$[%s]";
+ private static final String METADATA_VARIABLE_TEMPLATE = "${%s}";
+
@Test
public void testSimpleReplacement() {
String pattern = "ABC ${metadata_key} $[data_key]";
@@ -112,4 +119,27 @@ public class TbNodeUtilsTest {
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)));
+ }
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNodeConfiguration.java
index 3de9ca96c7..24736136f2 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNodeConfiguration.java
+++ b/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
public TbClearAlarmNodeConfiguration defaultConfiguration() {
TbClearAlarmNodeConfiguration configuration = new TbClearAlarmNodeConfiguration();
- configuration.setAlarmDetailsBuildJs("var details = {};\n" +
+ configuration.setAlarmDetailsBuildJs("" +
+ "//***DO NOT CHANGE THIS LINES***\n" +
+ "var details = {};\n" +
"if (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" +
+ "//***PLACE YOUR CODE BELOW***\n" +
+ "\n" +
+ "\n" +
"return details;");
configuration.setAlarmType("General Alarm");
return configuration;
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
index 3c3fa8e21a..03e0135715 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
+++ b/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;
-import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.json.JsonWriteFeature;
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.TbNodeException;
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.ReadTsKvQuery;
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_FIRST;
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.
@@ -64,6 +63,7 @@ import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
"If selected fetch mode ALL Telemetry will be added like array into Message Metadata where key is Timestamp and value is value of Telemetry." +
"If selected fetch mode FIRST or LAST Telemetry will be added like string without Timestamp." +
"Also, the rule node allows you to select telemetry sampling order: ASC or DESC. " +
+ "Aggregation feature allows you to fetch aggregated telemetry as a single value by AVG, COUNT, SUM, MIN, MAX, NONE. " +
"Note: The maximum size of the fetched array is 1000 records.\n ",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase")
@@ -78,6 +78,7 @@ public class TbGetTelemetryNode implements TbNode {
private ObjectMapper mapper;
private String fetchMode;
private String orderByFetchAll;
+ private Aggregation aggregation;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
@@ -89,11 +90,20 @@ public class TbGetTelemetryNode implements TbNode {
if (StringUtils.isEmpty(orderByFetchAll)) {
orderByFetchAll = ASC_ORDER;
}
+ aggregation = parseAggregationConfig(config.getAggregation());
+
mapper = new ObjectMapper();
mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false);
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
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
if (tsKeyNames.isEmpty()) {
@@ -120,8 +130,14 @@ public class TbGetTelemetryNode implements TbNode {
}
private List buildQueries(TbMsg msg, List 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()
- .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());
}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
index e4e673a2ab..1233191af5 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
+++ b/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 org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.server.common.data.kv.Aggregation;
import java.util.Collections;
import java.util.List;
@@ -46,6 +47,7 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration latestTsKeyNames;
@@ -63,6 +65,7 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration>> pendingFutures;
- TbHttpClient(TbRestApiCallNodeConfiguration config) throws TbNodeException {
+ TbHttpClient(TbRestApiCallNodeConfiguration config, EventLoopGroup eventLoopGroupShared) throws TbNodeException {
try {
this.config = config;
if (config.getMaxParallelRequestsCount() > 0) {
@@ -139,8 +139,7 @@ public class TbHttpClient {
}
httpClient = new AsyncRestTemplate();
} else {
- this.eventLoopGroup = new NioEventLoopGroup();
- Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(this.eventLoopGroup);
+ Netty4ClientHttpRequestFactory nettyFactory = new Netty4ClientHttpRequestFactory(getSharedOrCreateEventLoopGroup(eventLoopGroupShared));
nettyFactory.setSslContext(config.getCredentials().initSslContext());
nettyFactory.setReadTimeout(config.getReadTimeoutMs());
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 {
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"));
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
index 2ace27ef22..a23dbf820e 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
+++ b/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
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
TbRestApiCallNodeConfiguration config = TbNodeUtils.convert(configuration, TbRestApiCallNodeConfiguration.class);
- httpClient = new TbHttpClient(config);
+ httpClient = new TbHttpClient(config, ctx.getSharedEventLoop());
useRedisQueueForMsgPersistence = config.isUseRedisQueueForMsgPersistence();
if (useRedisQueueForMsgPersistence) {
log.warn("[{}][{}] Usage of Redis Template is deprecated starting 2.5 and will have no affect", ctx.getTenantId(), ctx.getSelfId());
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java
new file mode 100644
index 0000000000..533bdff03f
--- /dev/null
+++ b/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");
+ }
+
+}
diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java
new file mode 100644
index 0000000000..52e37abec9
--- /dev/null
+++ b/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));
+ }
+}
\ No newline at end of file