From 70679258d6a5ddda13b82a16ca32653378fc70cb Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 24 Sep 2021 15:11:00 +0300 Subject: [PATCH 01/49] Swagger annotations for saveDeviceCredentials --- .../server/controller/DeviceController.java | 14 ++++++++++++- common/data/pom.xml | 4 ++++ .../server/common/data/BaseData.java | 1 + .../server/common/data/id/DeviceId.java | 5 ++++- .../server/common/data/id/EntityId.java | 1 + .../server/common/data/id/UUIDBased.java | 5 +++++ .../data/security/DeviceCredentials.java | 20 ++++++++++++++++++- pom.xml | 6 ++++++ 8 files changed, 53 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 8cfa5ee2cf..06afa54833 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -19,9 +19,15 @@ 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.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiParam; +import io.swagger.annotations.Example; +import io.swagger.annotations.ExampleProperty; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -301,10 +307,16 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Updates device credentials.", 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 saveDeviceCredentials( + @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); 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/BaseData.java b/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java index 5baf3a9177..1eca49835f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java @@ -17,6 +17,7 @@ package org.thingsboard.server.common.data; import java.io.Serializable; +import io.swagger.annotations.ApiModelProperty; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.UUIDBased; 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/EntityId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java index 6057858fcd..10d51289e0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java @@ -18,6 +18,7 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import io.swagger.annotations.ApiModelProperty; import org.thingsboard.server.common.data.EntityType; import java.io.Serializable; 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/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java index 337e6cf349..8dd9d7855e 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; @EqualsAndHashCode(callSuper = true) +@ApiModel 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,19 @@ public class DeviceCredentials extends BaseData implements this.credentialsValue = deviceCredentials.getCredentialsValue(); } + @ApiModelProperty(position = 1, required = true, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9") + @Override + public DeviceCredentialsId getId() { + return super.getId(); + } + + @ApiModelProperty(position = 2, value = "long", example = "1609459200000") + @Override + public long getCreatedTime() { + return super.getCreatedTime(); + } + + @ApiModelProperty(position = 3, required = true) public DeviceId getDeviceId() { return deviceId; } @@ -54,6 +69,7 @@ public class DeviceCredentials extends BaseData implements this.deviceId = deviceId; } + @ApiModelProperty(position = 4, value = "string", allowableValues="ACCESS_TOKEN, X509_CERTIFICATE, MQTT_BASIC, LWM2M_CREDENTIALS") @Override public DeviceCredentialsType getCredentialsType() { return credentialsType; @@ -63,6 +79,7 @@ public class DeviceCredentials extends BaseData implements this.credentialsType = credentialsType; } + @ApiModelProperty(position = 5, required = true, value = "string", example = "By default, new access token for your device. Depends on the credentialsType.") @Override public String getCredentialsId() { return credentialsId; @@ -72,6 +89,7 @@ public class DeviceCredentials extends BaseData implements this.credentialsId = credentialsId; } + @ApiModelProperty(position = 6, required = false, value = "string", example = "Null in case of ACCESS_TOKEN, Depends on the credentialsType.") public String getCredentialsValue() { return credentialsValue; } diff --git a/pom.xml b/pom.xml index fa33548a56..b2ce88e87d 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 From 1de3f4d513b4749435c80bb35d654cf52bd57c05 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 24 Sep 2021 15:40:52 +0300 Subject: [PATCH 02/49] Code cleanup --- .../main/java/org/thingsboard/server/common/data/BaseData.java | 1 - .../java/org/thingsboard/server/common/data/id/EntityId.java | 1 - 2 files changed, 2 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java b/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java index 1eca49835f..5baf3a9177 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/BaseData.java @@ -17,7 +17,6 @@ package org.thingsboard.server.common.data; import java.io.Serializable; -import io.swagger.annotations.ApiModelProperty; import org.thingsboard.server.common.data.id.IdBased; import org.thingsboard.server.common.data.id.UUIDBased; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java index 10d51289e0..6057858fcd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityId.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.data.id; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import io.swagger.annotations.ApiModelProperty; import org.thingsboard.server.common.data.EntityType; import java.io.Serializable; From 5285cec69da2697b33113d4b883372bc5d3905c6 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Fri, 24 Sep 2021 18:51:31 +0300 Subject: [PATCH 03/49] Swagger Documentation for Save Device and Update Device Credentials --- .../server/controller/DeviceController.java | 12 ++++--- .../server/common/data/Device.java | 34 +++++++++++++++++++ .../data/DefaultDeviceConfiguration.java | 2 ++ .../data/device/data/DeviceConfiguration.java | 2 ++ .../common/data/device/data/DeviceData.java | 2 ++ .../data/DeviceTransportConfiguration.java | 2 ++ .../server/common/data/id/AlarmId.java | 5 ++- .../common/data/id/ApiUsageStateId.java | 5 ++- .../server/common/data/id/AssetId.java | 5 ++- .../server/common/data/id/CustomerId.java | 5 ++- .../server/common/data/id/DashboardId.java | 5 ++- .../common/data/id/DeviceProfileId.java | 3 +- .../server/common/data/id/EdgeId.java | 3 +- .../server/common/data/id/EntityViewId.java | 2 ++ .../server/common/data/id/OtaPackageId.java | 3 +- .../server/common/data/id/RpcId.java | 3 +- .../server/common/data/id/RuleChainId.java | 3 +- .../server/common/data/id/RuleNodeId.java | 3 +- .../server/common/data/id/TbResourceId.java | 3 +- .../server/common/data/id/TenantId.java | 3 +- .../common/data/id/TenantProfileId.java | 3 +- .../server/common/data/id/UserId.java | 3 +- .../server/common/data/id/WidgetTypeId.java | 3 +- .../common/data/id/WidgetsBundleId.java | 3 +- .../data/security/DeviceCredentials.java | 22 ++++++++---- 25 files changed, 111 insertions(+), 28 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 06afa54833..a61f4a2db3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -129,11 +129,15 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Create Or Update Device (saveDevice)", notes = "Platform generates random device Id and credentials (access token) during device creation. " + + "The device id will be present in the response. " + + "Specify the device id when you would like to update the device. Referencing non-existing device Id will cause 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()); @@ -307,14 +311,14 @@ public class DeviceController extends BaseController { } } - @ApiOperation(value = "Updates device credentials.", notes = "During device creation, platform generates random 'ACCESS_TOKEN' credentials. " + + @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( + public DeviceCredentials updateDeviceCredentials( @ApiParam(value = "A JSON value representing the device credentials.") @RequestBody DeviceCredentials deviceCredentials) throws ThingsboardException { checkNotNull(deviceCredentials); 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/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/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/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/security/DeviceCredentials.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/DeviceCredentials.java index 8dd9d7855e..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 @@ -22,8 +22,8 @@ import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.DeviceCredentialsId; import org.thingsboard.server.common.data.id.DeviceId; -@EqualsAndHashCode(callSuper = true) @ApiModel +@EqualsAndHashCode(callSuper = true) public class DeviceCredentials extends BaseData implements DeviceCredentialsFilter { private static final long serialVersionUID = -7869261127032877765L; @@ -48,19 +48,21 @@ public class DeviceCredentials extends BaseData implements this.credentialsValue = deviceCredentials.getCredentialsValue(); } - @ApiModelProperty(position = 1, required = true, value = "string", example = "784f394c-42b6-435a-983c-b7beff2784f9") + @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 = "long", example = "1609459200000") + @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) + @ApiModelProperty(position = 3, required = true, value = "JSON object with the device Id.") public DeviceId getDeviceId() { return deviceId; } @@ -69,7 +71,7 @@ public class DeviceCredentials extends BaseData implements this.deviceId = deviceId; } - @ApiModelProperty(position = 4, value = "string", allowableValues="ACCESS_TOKEN, X509_CERTIFICATE, MQTT_BASIC, LWM2M_CREDENTIALS") + @ApiModelProperty(position = 4, value = "Type of the credentials", allowableValues="ACCESS_TOKEN, X509_CERTIFICATE, MQTT_BASIC, LWM2M_CREDENTIALS") @Override public DeviceCredentialsType getCredentialsType() { return credentialsType; @@ -79,7 +81,11 @@ public class DeviceCredentials extends BaseData implements this.credentialsType = credentialsType; } - @ApiModelProperty(position = 5, required = true, value = "string", example = "By default, new access token for your device. Depends on the 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; @@ -89,7 +95,9 @@ public class DeviceCredentials extends BaseData implements this.credentialsId = credentialsId; } - @ApiModelProperty(position = 6, required = false, value = "string", example = "Null in case of ACCESS_TOKEN, Depends on the credentialsType.") + @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; } From 1d87ad73c49b534529c8d0bb58a620d26171b443 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 27 Sep 2021 12:11:00 +0300 Subject: [PATCH 04/49] Swagger Device Controller --- .../server/controller/DeviceController.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index a61f4a2db3..2bb23ccae7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -97,6 +97,7 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID; @RequiredArgsConstructor @Slf4j public class DeviceController extends BaseController { + public static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; private final DeviceBulkImportService deviceBulkImportService; private static final String DEVICE_ID = "deviceId"; @@ -235,10 +236,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)); @@ -266,10 +270,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)); From 55e8a4540adcc183b91404944eda213bf7f9769c Mon Sep 17 00:00:00 2001 From: Swoq Date: Tue, 28 Sep 2021 13:15:31 +0300 Subject: [PATCH 05/49] Swagger API docs for Device Controller --- .../server/controller/DeviceController.java | 165 +++++++++++++++--- 1 file changed, 144 insertions(+), 21 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 2bb23ccae7..8724625c1f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -19,15 +19,11 @@ 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.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; -import io.swagger.annotations.Example; -import io.swagger.annotations.ExampleProperty; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.PathVariable; @@ -97,17 +93,35 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID; @RequiredArgsConstructor @Slf4j public class DeviceController extends BaseController { - public static final String DEVICE_ID_PARAM_DESCRIPTION = "A string value representing the device id. For example, '784f394c-42b6-435a-983c-b7beff2784f9'"; private final DeviceBulkImportService deviceBulkImportService; + 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'"; + private static final String DEVICE_ID = "deviceId"; private static final String DEVICE_NAME = "deviceName"; private static final String TENANT_ID = "tenantId"; - + private final String PAGE_SIZE_DESCRIPTION = "Maximum amount of entities in a one page"; + private final String PAGE_NUMBER_DESCRIPTION = "Sequence number of page starting from 0"; + private final String DEVICE_TYPE_DESCRIPTION = "Device type as the name of the device profile"; + private final String DEVICE_TEXT_SEARCH_DESCRIPTION = "The search is performed by device special field 'textSearch' represented by device name"; + private final String SORT_PROPERTY_DESCRIPTION = "Property of device to sort by"; + private final String SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, label, type"; + private final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESCENDING (DESC)"; + private final String SORT_ORDER_ALLOWABLE_VALUES = "ASC, DESC"; + private final String DEVICE_INFO_DESCRIPTION = "Device Info is an object which are an extension of default Device object. " + + "Apart from Device object, Device Info provides additional information such as customer name and device profile name. "; + + @ApiOperation(value = "Get Device (getDeviceById)", + notes = "If device with given Id exists in the system it will be present in the response, otherwise an empty object will be provided") @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)); @@ -117,10 +131,12 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get Device Info (getDeviceInfoById)", notes = 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)); @@ -132,7 +148,7 @@ public class DeviceController extends BaseController { @ApiOperation(value = "Create Or Update Device (saveDevice)", notes = "Platform generates random device Id and credentials (access token) during device creation. " + "The device id will be present in the response. " + - "Specify the device id when you would like to update the device. Referencing non-existing device Id will cause error.") + "Specify the device id when you would like to update the device. Referencing non-existing device Id will cause an error.") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device", method = RequestMethod.POST) @ResponseBody @@ -175,10 +191,13 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Delete device (deleteDevice)", + notes = "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)); @@ -204,10 +223,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); @@ -242,7 +265,7 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/customer/device/{deviceId}", method = RequestMethod.DELETE) @ResponseBody public Device unassignDeviceFromCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) - @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); try { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); @@ -278,7 +301,7 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/customer/public/device/{deviceId}", method = RequestMethod.POST) @ResponseBody public Device assignDeviceToPublicCustomer(@ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) - @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { + @PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { checkParameter(DEVICE_ID, strDeviceId); try { DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); @@ -299,10 +322,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)); @@ -350,15 +376,24 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get Tenant Devices (getEdgeDevices)", + notes = "Returns a page (representation of a bunch) of devices in the possession of tenant. " + + "You can specify number of parameters to filter the result set of devices. ") @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantDevices( + @ApiParam(value = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) @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 = 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(); @@ -373,17 +408,28 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get Tenant Device Infos (getTenantDeviceInfos)", + notes = "Returns a page (representation of a bunch) of devices info objects in the possession of tenant. " + + "You can specify number of parameters to filter the result set. " + 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) @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) @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 = 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); @@ -400,6 +446,9 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get Tenant Device (getTenantDevice)", + notes = "Requested device must be in the possession of user that perform request. " + + "In the Thingsboard platform 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 @@ -413,16 +462,26 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get Customer Devices (getCustomerDevices)", + notes = "Returns a page (representation of a bunch) of devices objects in the possession of customer. " + + "You can specify number of parameters to filter the result set. ") @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) @PathVariable("customerId") String strCustomerId, + @ApiParam(value = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) @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 = 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 { @@ -440,17 +499,28 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get Customer Device Infos (getCustomerDeviceInfos)", + notes = "Returns a page (representation of a bunch) of devices info objects in the possession of customer. " + + "You can specify number of parameters to filter the result set. " + 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) @PathVariable("customerId") String strCustomerId, + @ApiParam(value = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) @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 = 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 { @@ -471,10 +541,13 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get Devices By Ids (getDevicesByIds)", + notes = "Requested devices must be in the possession of tenant or customer that performs 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 { @@ -521,6 +594,8 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get Device Types (getDeviceTypes)", + notes = "Returns all device profile names of all devices in the possession of user that is performing request.") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device/types", method = RequestMethod.GET) @ResponseBody @@ -535,10 +610,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 { @@ -589,10 +673,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<>(); @@ -640,10 +727,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); @@ -690,10 +781,17 @@ 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 ThingsBoard Edge. " + + "The ThingsBoard Edge is a ThingsBoard’s software product for edge computing. " + + "It allows bringing data analysis and management to the edge, while seamlessly synchronizing with ThingsBoard CE/PE server (cloud). " + + "See official documentation for more details regarding provisioning the edge on ThingsBoard server") @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); @@ -724,10 +822,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); @@ -758,18 +860,30 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Get devices assigned to edge (getEdgeDevices)", + notes = "Returns a page (representation of a bunch) of devices assigned to edge. " + + "You can specify number of parameters to filter the result set of devices. ") @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) @PathVariable(EDGE_ID) String strEdgeId, + @ApiParam(value = PAGE_SIZE_DESCRIPTION) @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) @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 = 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 { @@ -801,10 +915,17 @@ public class DeviceController extends BaseController { } } + @ApiOperation(value = "Count devices by device profile (countByDeviceProfileAndEmptyOtaPackage)", + notes = "The Thingsboard 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); @@ -818,6 +939,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 { From bcac3aaeedd50ebbf91dd2c3dfa38a1c08321247 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 11 Oct 2021 14:57:36 +0300 Subject: [PATCH 06/49] Swagger improvements --- .../server/controller/BaseController.java | 21 ++++++++ .../server/controller/DeviceController.java | 54 +++++++------------ 2 files changed, 40 insertions(+), 35 deletions(-) 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..ef4efdc6a8 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,27 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @TbCoreComponent public abstract class BaseController { + /*Swagger UI description*/ + public static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. "; + 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'"; + + 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 DEVICE_TEXT_SEARCH_DESCRIPTION = "The search is performed by device special field 'textSearch' represented by device name"; + protected final String SORT_PROPERTY_DESCRIPTION = "Property of device to sort by"; + protected final String SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, label, type"; + protected final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESCENDING (DESC)"; + protected final String SORT_ORDER_ALLOWABLE_VALUES = "ASC, DESC"; + protected final String DEVICE_INFO_DESCRIPTION = "Device Info is an object which are an extension of default Device object. " + + "Apart from Device object, Device Info provides additional information such as customer name and device profile name. "; + + + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; protected static final String DEFAULT_DASHBOARD = "defaultDashboardId"; protected static final String HOME_DASHBOARD = "homeDashboardId"; 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 8724625c1f..e0cc34e043 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -93,27 +93,12 @@ import static org.thingsboard.server.controller.EdgeController.EDGE_ID; @RequiredArgsConstructor @Slf4j public class DeviceController extends BaseController { - private final DeviceBulkImportService deviceBulkImportService; - 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'"; - - private static final String DEVICE_ID = "deviceId"; - private static final String DEVICE_NAME = "deviceName"; - private static final String TENANT_ID = "tenantId"; - private final String PAGE_SIZE_DESCRIPTION = "Maximum amount of entities in a one page"; - private final String PAGE_NUMBER_DESCRIPTION = "Sequence number of page starting from 0"; - private final String DEVICE_TYPE_DESCRIPTION = "Device type as the name of the device profile"; - private final String DEVICE_TEXT_SEARCH_DESCRIPTION = "The search is performed by device special field 'textSearch' represented by device name"; - private final String SORT_PROPERTY_DESCRIPTION = "Property of device to sort by"; - private final String SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, label, type"; - private final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESCENDING (DESC)"; - private final String SORT_ORDER_ALLOWABLE_VALUES = "ASC, DESC"; - private final String DEVICE_INFO_DESCRIPTION = "Device Info is an object which are an extension of default Device object. " + - "Apart from Device object, Device Info provides additional information such as customer name and device profile name. "; + 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 = "If device with given Id exists in the system it will be present in the response, otherwise an empty object will be provided") @@ -377,7 +362,7 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get Tenant Devices (getEdgeDevices)", - notes = "Returns a page (representation of a bunch) of devices in the possession of tenant. " + + notes = "Returns a page of devices owned by tenant. " + "You can specify number of parameters to filter the result set of devices. ") @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @@ -409,8 +394,8 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get Tenant Device Infos (getTenantDeviceInfos)", - notes = "Returns a page (representation of a bunch) of devices info objects in the possession of tenant. " + - "You can specify number of parameters to filter the result set. " + DEVICE_INFO_DESCRIPTION) + 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 @@ -447,8 +432,8 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get Tenant Device (getTenantDevice)", - notes = "Requested device must be in the possession of user that perform request. " + - "In the Thingsboard platform Device name is an unique property of device. So it can be used to identify the device.") + notes = "Requested device must be owned by tenant of customer 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 @@ -463,8 +448,8 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get Customer Devices (getCustomerDevices)", - notes = "Returns a page (representation of a bunch) of devices objects in the possession of customer. " + - "You can specify number of parameters to filter the result set. ") + 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 @@ -500,8 +485,8 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get Customer Device Infos (getCustomerDeviceInfos)", - notes = "Returns a page (representation of a bunch) of devices info objects in the possession of customer. " + - "You can specify number of parameters to filter the result set. " + DEVICE_INFO_DESCRIPTION) + 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 @@ -782,10 +767,9 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Assign device to edge (assignDeviceToEdge)", - notes = "Creates assignment of an existing device to an instance of The ThingsBoard Edge. " + - "The ThingsBoard Edge is a ThingsBoard’s software product for edge computing. " + - "It allows bringing data analysis and management to the edge, while seamlessly synchronizing with ThingsBoard CE/PE server (cloud). " + - "See official documentation for more details regarding provisioning the edge on ThingsBoard server") + 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 @@ -861,7 +845,7 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get devices assigned to edge (getEdgeDevices)", - notes = "Returns a page (representation of a bunch) of devices assigned to edge. " + + notes = "Returns a page of devices assigned to edge. " + "You can specify number of parameters to filter the result set of devices. ") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @@ -916,7 +900,7 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Count devices by device profile (countByDeviceProfileAndEmptyOtaPackage)", - notes = "The Thingsboard platform gives an ability to load OTA (over-the-air) packages to devices. " + + 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") From d6370ed64d4baa1610a0fb215f707366167791ec Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 11 Oct 2021 15:47:33 +0300 Subject: [PATCH 07/49] Documentation for PageData, DeviceSearchQuery and DeviceInfo --- .../server/controller/BaseController.java | 4 +++- .../server/controller/DeviceController.java | 12 ++++++++---- .../thingsboard/server/common/data/DeviceInfo.java | 6 ++++++ .../server/common/data/device/DeviceSearchQuery.java | 6 ++++++ .../server/common/data/page/PageData.java | 7 +++++++ .../data/relation/RelationsSearchParameters.java | 9 +++++++++ 6 files changed, 39 insertions(+), 5 deletions(-) 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 ef4efdc6a8..f70232911b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -154,7 +154,9 @@ import static org.thingsboard.server.dao.service.Validator.validateId; public abstract class BaseController { /*Swagger UI description*/ - public static final String PAGE_DATA_PARAMETERS = "You can specify parameters to filter the results. "; + 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 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'"; 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 e0cc34e043..179f3da508 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -363,7 +363,7 @@ public class DeviceController extends BaseController { @ApiOperation(value = "Get Tenant Devices (getEdgeDevices)", notes = "Returns a page of devices owned by tenant. " + - "You can specify number of parameters to filter the result set of devices. ") + PAGE_DATA_PARAMETERS) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody @@ -527,7 +527,7 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get Devices By Ids (getDevicesByIds)", - notes = "Requested devices must be in the possession of tenant or customer that performs request. ") + 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 @@ -555,6 +555,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 @@ -580,7 +584,7 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get Device Types (getDeviceTypes)", - notes = "Returns all device profile names of all devices in the possession of user that is performing request.") + 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 @@ -846,7 +850,7 @@ public class DeviceController extends BaseController { @ApiOperation(value = "Get devices assigned to edge (getEdgeDevices)", notes = "Returns a page of devices assigned to edge. " + - "You can specify number of parameters to filter the result set of devices. ") + PAGE_DATA_PARAMETERS) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody 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/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/page/PageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java index 6ffbce4d3d..4b0ace9f68 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/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) { From bf56d9e1a44093cfb6b4437f9cf0785b6e108875 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 11 Oct 2021 16:42:22 +0300 Subject: [PATCH 08/49] Customer Controller API description --- .../server/controller/BaseController.java | 6 +- .../server/controller/CustomerController.java | 58 +++++++++++++++---- .../server/controller/DeviceController.java | 16 +++-- 3 files changed, 61 insertions(+), 19 deletions(-) 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 f70232911b..ad54aaf526 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -166,13 +166,13 @@ public abstract class BaseController { 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 DEVICE_TEXT_SEARCH_DESCRIPTION = "The search is performed by device special field 'textSearch' represented by device name"; + 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 name."; protected final String SORT_PROPERTY_DESCRIPTION = "Property of device to sort by"; protected final String SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, label, type"; protected final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESCENDING (DESC)"; protected final String SORT_ORDER_ALLOWABLE_VALUES = "ASC, DESC"; - protected final String DEVICE_INFO_DESCRIPTION = "Device Info is an object which are an extension of default Device object. " + - "Apart from Device object, Device Info provides additional information such as customer name and device profile name. "; + 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. "; 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..28a40706b9 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; @@ -52,16 +54,22 @@ 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 +78,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 +101,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 +119,14 @@ public class CustomerController extends BaseController { } } + @ApiOperation(value = "Create or update Customer (saveCustomer)", + notes = "Creates or Updates the Customer. Platform generates random Customer Id during device creation. " + + "The Customer Id will be present in the response. Specify the Customer Id when you would like to update the Customer. " + + "Referencing non-existing Customer Id will cause an 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) + @RequestParam int pageSize, + @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @RequestParam int page, + @ApiParam(value = CUSTOMER_TEXT_SEARCH_DESCRIPTION) + @RequestParam(required = false) String textSearch, + @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = 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/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 179f3da508..14c234c82f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -101,7 +101,9 @@ public class DeviceController extends BaseController { private final DeviceBulkImportService deviceBulkImportService; @ApiOperation(value = "Get Device (getDeviceById)", - notes = "If device with given Id exists in the system it will be present in the response, otherwise an empty object will be provided") + 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 @@ -116,7 +118,10 @@ public class DeviceController extends BaseController { } } - @ApiOperation(value = "Get Device Info (getDeviceInfoById)", notes = DEVICE_INFO_DESCRIPTION) + @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 @@ -131,7 +136,8 @@ public class DeviceController extends BaseController { } } - @ApiOperation(value = "Create Or Update Device (saveDevice)", notes = "Platform generates random device Id and credentials (access token) during device creation. " + + @ApiOperation(value = "Create Or Update Device (saveDevice)", + notes = "Creates or Updates the Device. Platform generates random device Id and credentials (access token) during device creation. " + "The device id will be present in the response. " + "Specify the device id when you would like to update the device. Referencing non-existing device Id will cause an error.") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @@ -177,7 +183,7 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Delete device (deleteDevice)", - notes = "Referencing non-existing device Id will cause an error.") + notes = "Deletes the device and it's credentials. Referencing non-existing device Id will cause an error.") @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/device/{deviceId}", method = RequestMethod.DELETE) @ResponseStatus(value = HttpStatus.OK) @@ -361,7 +367,7 @@ public class DeviceController extends BaseController { } } - @ApiOperation(value = "Get Tenant Devices (getEdgeDevices)", + @ApiOperation(value = "Get Tenant Devices (getTenantDevices)", notes = "Returns a page of devices owned by tenant. " + PAGE_DATA_PARAMETERS) @PreAuthorize("hasAuthority('TENANT_ADMIN')") From e6e05c9b885f3f18a0556e3a5e6804bb74372667 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 11 Oct 2021 17:26:29 +0300 Subject: [PATCH 09/49] Admin Controller description --- .../server/controller/AdminController.java | 45 ++++++++++++++++--- .../server/common/data/AdminSettings.java | 18 ++++++++ .../server/common/data/UpdateMessage.java | 5 +++ .../data/security/model/SecuritySettings.java | 7 ++- .../security/model/UserPasswordPolicy.java | 10 +++++ .../AwsSnsSmsProviderConfiguration.java | 6 +++ .../data/sms/config/TestSmsRequest.java | 6 +++ .../TwilioSmsProviderConfiguration.java | 6 +++ 8 files changed, 96 insertions(+), 7 deletions(-) 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/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/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/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 From b31f0748c820aa3e426b50ca6ae1a20de1098ea0 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 29 Sep 2021 23:50:15 +0300 Subject: [PATCH 10/49] DefaultTbCoreToTransportService - skipping message without nodeId --- .../service/transport/DefaultTbCoreToTransportService.java | 4 ++++ 1 file changed, 4 insertions(+) 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..1208518e3c 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,10 @@ 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); + 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); From 4c0812898cb1a07c87bac73bf6ce3b7fe2f92aec Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 29 Sep 2021 18:04:29 +0300 Subject: [PATCH 11/49] Skip Notification To Transport without ServiceId --- .../server/service/queue/DefaultTbClusterService.java | 4 ++++ .../server/service/rpc/DefaultTbRuleEngineRpcService.java | 4 ++++ 2 files changed, 8 insertions(+) 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..7ad22a7a8b 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,10 @@ 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); + 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(); From d25c605ac617be3ccfeae644fa3f3b22325f4b3d Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Oct 2021 12:36:46 +0300 Subject: [PATCH 12/49] DefaultTbCoreToTransportService: fire "onSuccess send" when skipping message without nodeId --- .../service/transport/DefaultTbCoreToTransportService.java | 3 +++ 1 file changed, 3 insertions(+) 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 1208518e3c..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 @@ -56,6 +56,9 @@ public class DefaultTbCoreToTransportService implements TbCoreToTransportService 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); From 7a2a4b9d01dea906fb9588154d7280bf38df4c04 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Sat, 18 Sep 2021 16:59:14 +0300 Subject: [PATCH 13/49] TbGetTelemetryNode Aggregation feature allows you to fetch aggregated telemetry as a single value by AVG, COUNT, SUM, MIN, MAX, NONE. --- .../engine/metadata/TbGetTelemetryNode.java | 16 +++- .../TbGetTelemetryNodeConfiguration.java | 3 + .../metadata/TbGetTelemetryNodeTest.java | 83 +++++++++++++++++++ 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java 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..8bc3336fe5 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()) { @@ -121,7 +131,7 @@ public class TbGetTelemetryNode implements TbNode { private List buildQueries(TbMsg msg, List keys) { return keys.stream() - .map(key -> new BaseReadTsKvQuery(key, getInterval(msg).getStartTs(), getInterval(msg).getEndTs(), 1, limit, NONE, getOrderBy())) + .map(key -> new BaseReadTsKvQuery(key, getInterval(msg).getStartTs(), getInterval(msg).getEndTs(), 1, 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 Date: Tue, 28 Sep 2021 16:38:53 +0300 Subject: [PATCH 14/49] TbGetTelemetryNode Aggregation feature - count aggIntervalStep when buildQueries for aggregation --- .../rule/engine/metadata/TbGetTelemetryNode.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 8bc3336fe5..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 @@ -130,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, aggregation, getOrderBy())) + .map(key -> new BaseReadTsKvQuery(key, interval.getStartTs(), interval.getEndTs(), aggIntervalStep, limit, aggregation, getOrderBy())) .collect(Collectors.toList()); } From 3820d2bb1c4fa375ca76f3f37d896aa681e67e4a Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Oct 2021 14:08:21 +0300 Subject: [PATCH 15/49] TbGetTelemetryNodeTest: fixed CE licence header --- .../metadata/TbGetTelemetryNodeTest.java | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) 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 index d20685329a..533bdff03f 100644 --- 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 @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.thingsboard.rule.engine.metadata; From 5ad33bafc6052f41d4ee223e21308687bd1aada7 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 24 Sep 2021 10:45:54 +0300 Subject: [PATCH 16/49] clear alarm node: default script changed to avoid infinite metadata grow --- .../engine/action/TbClearAlarmNodeConfiguration.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) 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..b2641cfdd5 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" + + " metadata.delete('prevAlarmDetails')\n" + + " //now metadata is the same as it comes to this rule node" + "}\n" + + "//***PLACE YOU CODE BELOW***\n" + + "\n" + + "\n" + "return details;"); configuration.setAlarmType("General Alarm"); return configuration; From 6d468036e75555cfa1804b610a98a5ee4dd6dab2 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Oct 2021 14:46:03 +0300 Subject: [PATCH 17/49] clear alarm node: fixed typo --- .../rule/engine/action/TbClearAlarmNodeConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 b2641cfdd5..6bc3169487 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 @@ -32,9 +32,9 @@ public class TbClearAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigurat " details = JSON.parse(metadata.prevAlarmDetails);\n" + " //remove prevAlarmDetails from metadata\n" + " metadata.delete('prevAlarmDetails')\n" + - " //now metadata is the same as it comes to this rule node" + + " //now metadata is the same as it comes IN this rule node" + "}\n" + - "//***PLACE YOU CODE BELOW***\n" + + "//***PLACE YOUR CODE BELOW***\n" + "\n" + "\n" + "return details;"); From e755f3f6ed1d02cfcfb14b031cad302b19f60486 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 12 Oct 2021 15:19:38 +0300 Subject: [PATCH 18/49] Description of the Auth Controller --- .../server/controller/AuthController.java | 86 +++++++++++++------ .../server/controller/DeviceController.java | 8 +- .../security/model/ActivateUserRequest.java | 30 +++++++ .../security/model/ChangePasswordRequest.java | 31 +++++++ .../service/security/model/JwtTokenPair.java | 32 +++++++ .../model/ResetPasswordEmailRequest.java | 29 +++++++ .../security/model/ResetPasswordRequest.java | 30 +++++++ .../thingsboard/server/common/data/User.java | 33 +++++++ 8 files changed, 252 insertions(+), 27 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/security/model/ActivateUserRequest.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/model/ChangePasswordRequest.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordEmailRequest.java create mode 100644 application/src/main/java/org/thingsboard/server/service/security/model/ResetPasswordRequest.java 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/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index 14c234c82f..f8d50693a7 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -137,9 +137,11 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Create Or Update Device (saveDevice)", - notes = "Creates or Updates the Device. Platform generates random device Id and credentials (access token) during device creation. " + - "The device id will be present in the response. " + - "Specify the device id when you would like to update the device. Referencing non-existing device Id will cause an error.") + notes = "Creates or Updates 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 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/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(); From 63d33ba1bfb1f186e2abee9b58762c5566132f2a Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Oct 2021 15:40:29 +0300 Subject: [PATCH 19/49] clear alarm node: fixed "delete metadata.prevAlarmDetails" --- .../rule/engine/action/TbClearAlarmNodeConfiguration.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6bc3169487..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 @@ -31,7 +31,7 @@ public class TbClearAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigurat "if (metadata.prevAlarmDetails) {\n" + " details = JSON.parse(metadata.prevAlarmDetails);\n" + " //remove prevAlarmDetails from metadata\n" + - " metadata.delete('prevAlarmDetails')\n" + + " delete metadata.prevAlarmDetails;\n" + " //now metadata is the same as it comes IN this rule node" + "}\n" + "//***PLACE YOUR CODE BELOW***\n" + From ca89a4fc0e2c4c20b9c60bf27ee4f1749aa5744a Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 12 Oct 2021 16:17:34 +0300 Subject: [PATCH 20/49] Component Descriptor documentation --- .../ComponentDescriptorController.java | 34 ++++++++++++++++--- .../data/plugin/ComponentDescriptor.java | 26 +++++++++++++- 2 files changed, 54 insertions(+), 6 deletions(-) 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/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; } + } From 4ea397b4d59fadaf54585b8b607e66e26a10fe27 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Oct 2021 17:16:14 +0300 Subject: [PATCH 21/49] ClusterService - pushNotificationToTransport - fire callback.onSuccess(null) if serviceId is empty --- .../server/service/queue/DefaultTbClusterService.java | 3 +++ 1 file changed, 3 insertions(+) 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 7ad22a7a8b..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 @@ -198,6 +198,9 @@ public class DefaultTbClusterService implements TbClusterService { 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); From 18800c57c8ec14955f32fed13100c76e570e21da Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 27 Aug 2021 17:45:50 +0300 Subject: [PATCH 22/49] tests: added first test to the common/data package (maven plugin added as well) --- common/data/pom.xml | 18 ++++++++++++++++++ .../server/common/data/id/EntityIdTest.java | 13 +++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java diff --git a/common/data/pom.xml b/common/data/pom.xml index 3b9152fb47..f8138928d6 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -119,6 +119,24 @@ false + + org.apache.maven.plugins + maven-surefire-plugin + ${surfire.version} + + + thingsboard + + + **/sql/*Test.java + **/nosql/*Test.java + + + **/*Test.java + **/*TestSuite.java + + +
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..2aa67db36d --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/id/EntityIdTest.java @@ -0,0 +1,13 @@ +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 From 7f3d63512e49932a2b2c5462dd44a9ae01e10007 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 27 Aug 2021 17:22:09 +0300 Subject: [PATCH 23/49] tests: added license header --- .../server/common/data/id/EntityIdTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 index 2aa67db36d..a53124860c 100644 --- 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 @@ -1,3 +1,18 @@ +/** + * 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; From a5d446914cae8fcb64ce5b0683285272d7cf2ec0 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 30 Aug 2021 11:58:29 +0300 Subject: [PATCH 24/49] tests: relation query - added test cases with many edges between two nodes, horizontal ring (loop) on the same level, loop Tenant-Asset-Device-Tenant. This commit will fail 4 hierarchy tests. Reformat code --- .../dao/service/BaseEntityServiceTest.java | 70 ++++++++++++++----- 1 file changed, 52 insertions(+), 18 deletions(-) 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..2bd11f0914 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 @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; 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; @@ -82,6 +83,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import static org.junit.Assert.assertEquals; +import static org.hamcrest.MatcherAssert.assertThat; public abstract class BaseEntityServiceTest extends AbstractServiceTest { @@ -110,7 +112,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { tenantService.deleteTenant(tenantId); } - + @Test public void testCountEntitiesByQuery() throws InterruptedException { List devices = new ArrayList<>(); @@ -159,7 +161,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { 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); @@ -308,7 +310,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { 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++) { @@ -373,14 +375,14 @@ 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++) { @@ -446,14 +448,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,7 +520,7 @@ 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 { + private void createTestHierarchy(TenantId tenantId, List assets, List devices, List consumptions, List highConsumptions, List temperatures, List highTemperatures) throws InterruptedException { for (int i = 0; i < 5; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); @@ -563,9 +565,41 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { } } } + + 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()); + } + + 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 -> relationService.saveRelation(tenantId, new EntityRelation(from, toId, type, RelationTypeGroup.COMMON))); } - @Test public void testSimpleFindEntityDataByQuery() throws InterruptedException { List devices = new ArrayList<>(); @@ -871,7 +905,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 +1065,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { deviceService.deleteDevicesByTenantId(tenantId); } - + @Test public void testFindEntityDataByQueryWithTimeseries() throws ExecutionException, InterruptedException { @@ -1122,7 +1156,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 +1176,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 +1336,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 +1453,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { } @Test - public void testBuildSimplePredicateQueryOperations() throws InterruptedException{ + public void testBuildSimplePredicateQueryOperations() throws InterruptedException { List devices = new ArrayList<>(); @@ -1492,7 +1526,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 +1537,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(); From 891024f62011d8d491b67d96479d3d8c01dcb1ce Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 1 Sep 2021 14:59:49 +0300 Subject: [PATCH 25/49] relation-query-improvement, failed relation were disabled temporary and will be enabled on next commits --- .../query/DefaultEntityQueryRepository.java | 23 ++++++++++++++----- .../dao/service/BaseEntityServiceTest.java | 6 ++--- 2 files changed, 20 insertions(+), 9 deletions(-) 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..d47622cb8c 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 @@ -239,18 +239,29 @@ 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" + + 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'" + " 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"); 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 2bd11f0914..68632802b6 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 @@ -567,10 +567,10 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { } createManyCustomRelationsBetweenTwoNodes(tenantId, "UseCase", assets, devices); - createHorizontalRingRelations(tenantId, "Ring(Loop)-Ast", assets); + //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()); + //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()); } void createLoopRelations(TenantId tenantId, String type, EntityId... ids) { From 2d2bd2c1586aed1012aae8dd7ccc43dbade0ca8e Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 3 Sep 2021 08:54:00 +0300 Subject: [PATCH 26/49] reverted unnecessary declaration for maven-surefire-plugin on common/data where only simple test present (will run by default) --- common/data/pom.xml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/common/data/pom.xml b/common/data/pom.xml index f8138928d6..3b9152fb47 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -119,24 +119,6 @@ false - - org.apache.maven.plugins - maven-surefire-plugin - ${surfire.version} - - - thingsboard - - - **/sql/*Test.java - **/nosql/*Test.java - - - **/*Test.java - **/*TestSuite.java - - - From 9ceb6f0d4d635d6efdb4a944b4b9cdcae96b575e Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 3 Sep 2021 15:15:15 +0300 Subject: [PATCH 27/49] relational query: stable test run with ManyCustomRelationsBetweenTwoNodes, but without Loop Relations --- .../thingsboard/server/dao/service/BaseEntityServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 68632802b6..1eea71a408 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 @@ -568,7 +568,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { 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-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()); } From 99476c78c9fc165ea00970f25d27001df4a735b0 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 3 Sep 2021 15:33:40 +0300 Subject: [PATCH 28/49] relational query failed test when createHorizontalRingRelations added and freeze when createLoopRelations (due to infinite recursion with no level limit) --- .../thingsboard/server/dao/service/BaseEntityServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1eea71a408..877bf366b5 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 @@ -567,7 +567,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { } createManyCustomRelationsBetweenTwoNodes(tenantId, "UseCase", assets, devices); - //createHorizontalRingRelations(tenantId, "Ring(Loop)-Ast", assets); + 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()); From e4f8c7e8788d5551a4d2c8b296b884d2fb337756 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 3 Sep 2021 16:05:14 +0300 Subject: [PATCH 29/49] relational query: Limit the max level to break the infinite loop when circles appear in relation graph --- .../query/DefaultEntityQueryRepository.java | 9 +++- .../dao/service/BaseEntityServiceTest.java | 6 +-- .../DefaultEntityQueryRepositoryTest.java | 42 +++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepositoryTest.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 d47622cb8c..e1c3a83936 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 = 10; //This value has to be reasonable small to prevent infinite recursion as early as possible static { entityTableMap.put(EntityType.ASSET, "asset"); @@ -704,8 +705,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/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java index 877bf366b5..2bd11f0914 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 @@ -568,9 +568,9 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { 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()); + 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()); } void createLoopRelations(TenantId tenantId, String type, EntityId... ids) { 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..6e67a4946f --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepositoryTest.java @@ -0,0 +1,42 @@ +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)); + } + +} From d65b02df19da10c97ca4c11aea1910e5721e89e5 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 6 Sep 2021 11:18:06 +0300 Subject: [PATCH 30/49] test: count entities by query adjusted due to the loop relations in hierarchy --- .../thingsboard/server/dao/service/BaseEntityServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 2bd11f0914..f8dd6cc344 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 @@ -170,7 +170,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); From f2f34680779cbf6aab7af1600a698e85e45eeb3f Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Mon, 6 Sep 2021 13:00:34 +0300 Subject: [PATCH 31/49] test relations: print all relations added as sql procedure to reproduce exact data in the PostgreSQL --- .../dao/service/BaseEntityServiceTest.java | 47 ++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) 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 f8dd6cc344..357af4e644 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 @@ -70,6 +70,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; @@ -98,6 +99,9 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { @Autowired private JdbcTemplate template; + @Autowired + private RelationRepository relationRepository; + @Before public void before() { Tenant tenant = new Tenant(); @@ -156,7 +160,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { Assert.assertEquals(0, count); } - + @Test public void testCountHierarchicalEntitiesByQuery() throws InterruptedException { List assets = new ArrayList<>(); @@ -571,6 +575,47 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { 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(); + } + + /* + * 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) { From edd96624ae6868ab1f06ee382cbabb8e128fcffb Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 7 Sep 2021 16:26:25 +0300 Subject: [PATCH 32/49] relation query: group by added to the initial pert of recursive query --- .../server/dao/sql/query/DefaultEntityQueryRepository.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 e1c3a83936..22d2ac62f2 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,7 +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 = 10; //This value has to be reasonable small to prevent infinite recursion as early as possible + 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"); @@ -244,8 +244,9 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " SELECT from_id, from_type, to_id, to_type," + " 1 as lvl," + " ARRAY[$in_id] as path" + // initial path - " FROM relation" + + " 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," + " (re.lvl + 1) as lvl, " + From 1adaef31f529c82f34349215d5919634ba85f1c8 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Oct 2021 18:28:44 +0300 Subject: [PATCH 33/49] license header CE fixed for DefaultEntityQueryRepositoryTest --- .../query/DefaultEntityQueryRepositoryTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) 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 index 6e67a4946f..a0c716d51f 100644 --- 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 @@ -1,3 +1,18 @@ +/** + * 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; From adb9a8d1501bf37e3ba918ef2b8a5617508555cd Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 7 Sep 2021 20:23:24 +0300 Subject: [PATCH 34/49] dao tests: PostgreSqlDaoServiceTestSuite added using org.testcontainers --- dao/pom.xml | 15 ++ .../dao/PostgreSqlDaoServiceTestSuite.java | 76 ++++++++ .../dao/service/BaseEntityServiceTest.java | 177 ++++++++++++++++-- .../server/dao/service/DaoPostgreSqlTest.java | 48 +++++ .../EntityServicePostgreSqlTest.java} | 8 +- dao/src/test/resources/psql-test.properties | 47 +++++ .../test/resources/sql/system-test-psql.sql | 2 + pom.xml | 11 ++ 8 files changed, 363 insertions(+), 21 deletions(-) create mode 100644 dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java rename dao/src/test/java/org/thingsboard/server/dao/service/{sql/EntityServiceSqlTest.java => psql/EntityServicePostgreSqlTest.java} (77%) create mode 100644 dao/src/test/resources/psql-test.properties create mode 100644 dao/src/test/resources/sql/system-test-psql.sql diff --git a/dao/pom.xml b/dao/pom.xml index 922b389bff..1488d7eec5 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 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..5d6f1a374f --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java @@ -0,0 +1,76 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.dao; + +import org.junit.ClassRule; +import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters; +import org.junit.runner.RunWith; + +import java.util.Arrays; + +@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 { + + @ClassRule + public static CustomSqlUnit sqlUnit = new CustomSqlUnit( + Arrays.asList("sql/schema-ts-psql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql" + , "sql/system-data.sql" + , "sql/system-test-psql.sql" + ), + "sql/psql/drop-all-tables.sql", + "psql-test.properties" + ); + +// @ClassRule +// public static CustomSqlUnit sqlUnit = new CustomSqlUnit( +// Arrays.asList("sql/schema-ts-psql.sql" +// , "sql/schema-entities.sql", "sql/schema-entities-idx.sql" +// , "sql/system-data.sql", "sql/system-test.sql" +// ), +// "sql/psql/drop-all-tables.sql", +// "sql-test.properties" +// ); + +// @ClassRule +// public static CustomSqlUnit sqlUnit = new CustomSqlUnit( +// Arrays.asList("sql/schema-timescale.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), +// "sql/timescale/drop-all-tables.sql", +// "sql-test.properties" +// ); + +} 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 357af4e644..fbace01ddb 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,15 +17,18 @@ 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.jetbrains.annotations.NotNull; 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; @@ -80,14 +83,17 @@ import java.util.Comparator; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicInteger; 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; @@ -525,7 +531,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { } private void createTestHierarchy(TenantId tenantId, List assets, List devices, List consumptions, List highConsumptions, List temperatures, List highTemperatures) throws InterruptedException { - for (int i = 0; i < 5; i++) { + for (int i = 0; i < ENTITY_COUNT; i++) { Asset asset = new Asset(); asset.setTenantId(tenantId); asset.setName("Asset" + i); @@ -535,18 +541,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); @@ -556,27 +563,158 @@ 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()); + } } } - createManyCustomRelationsBetweenTwoNodes(tenantId, "UseCase", assets, devices); + //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(); + //testQuery(); + + //printAllRelations(); + } + + private void testQuery() { + + template.query("" + + " 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" + + " );\n", r -> null); + + log.error("insert test {}", (Object) template.query("" + + "INSERT INTO relation_test (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) " + + " VALUES " + + " ('11111111-0f19-11ec-ba23-e981fc95500d', 'TENANT', '22222222-0f19-11ec-ba23-e981fc95500d', 'ASSET', 'COMMON', 'Contains', null),\n" + + " ('22222222-0f19-11ec-ba23-e981fc95500d', 'ASSET', '33333333-0f19-11ec-ba23-e981fc95500d', 'DEVICE', 'COMMON', 'Contains', null),\n" + + " ('33333333-0f19-11ec-ba23-e981fc95500d', 'DEVICE', '11111111-0f19-11ec-ba23-e981fc95500d', 'TENANT', 'COMMON', 'Contains', null);\n" + + "" + , r -> null)); + + + //log.error("array_position hsql {}", template.queryForObject("select array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon') from relation_test", Long.class)); + + log.error("array_position hsql {}", template.queryForObject("select position_array('mon' in ARRAY['sun','mon','tue']);", Long.class)); + log.error("array_position psql {}", template.queryForObject("select array_position(ARRAY['sun','mon','tue'], 'mon');", Long.class)); + + Long countTest = template.queryForObject("select count(*) from relation_test", Long.class); + log.error("count test {}", countTest); + + Long count = template.queryForObject("select count(*) from relation", Long.class); + log.error("count {}", count); + + List> result = template.query("" + + "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 [from_id] as path " + + " FROM relation_test r " + + " WHERE from_id = '11111111-0f19-11ec-ba23-e981fc95500d' " + + " and from_type = 'TENANT' " + + " and relation_type_group = 'COMMON' " + + " GROUP BY r.from_id, r.from_type, r.to_id, r.to_type , 1, ARRAY [from_id] " + + " UNION ALL " + + " SELECT r.from_id, " + + " r.from_type, " + + " r.to_id, " + + " r.to_type, " + + " (re.lvl + 1) as lvl, " + + " (re.path || ARRAY [r.from_id]) as path " + + " FROM relation_test r " + + " INNER JOIN related_entities re " + + " ON r.from_id = re.to_id and " + + " r.from_type = re.to_type and " + + " relation_type_group = 'COMMON' and " + + " r.from_id NOT IN (SELECT * FROM unnest(re.path)) and " + + " re.lvl <= 7 " + + " GROUP BY r.from_id, r.from_type, r.to_id, r.to_type, " + + " (re.lvl + 1), (re.path || ARRAY [r.from_id])) " + + " " + + " SELECT lvl, from_id, from_type, to_id, to_type, path " + //to_id IN (SELECT * FROM unnest(path)) as is_present, + " from related_entities r_int " + + " " + + " ", + getListResultSetExtractor()); + + log.error("result {}", result.size() - 1); + AtomicInteger counter = new AtomicInteger(); + result.forEach(r -> System.out.printf("%s %s\n", counter.incrementAndGet(), r.toString())); + log.error("end"); + + log.error("query 1 (expected true): {}", template.queryForObject( + "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3'), UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')])) ", + String.class)); + log.error("query 1.1 (expected true): {}", template.queryForObject( + "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')] || ARRAY[UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')] )) ", + String.class)); + log.error("query 2 (expected true): {}", template.queryForObject( + "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')) ", + String.class)); +// log.error("query true3: ", template.queryForObject( +// "SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3' IN (SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')) ", +// String.class)); + log.error("query 3 (expected true): {} ", template.queryForObject( + "SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3' NOT IN (SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3') ", + String.class)); + + List> result2 = template.query("SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3'), UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')]) " + , getListResultSetExtractor()); + log.error("result2 {}", result2.size() - 1); + AtomicInteger counter2 = new AtomicInteger(); + result2.forEach(r -> System.out.printf("%s %s\n", counter2.incrementAndGet(), r.toString())); + log.error("end2"); + + } + + @NotNull + 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; + }; } /* @@ -642,9 +780,14 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { } void createOneToManyRelations(TenantId tenantId, String type, EntityId from, List toIds) { - toIds.forEach(toId -> relationService.saveRelation(tenantId, new EntityRelation(from, toId, type, RelationTypeGroup.COMMON))); + 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<>(); 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..16da531369 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java @@ -0,0 +1,48 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.dao.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/resources/psql-test.properties b/dao/src/test/resources/psql-test.properties new file mode 100644 index 0000000000..00420be628 --- /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 +spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver +#org.postgresql.Driver +spring.datasource.hikari.maximumPoolSize=20 +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..2cc0f0d6b1 100755 --- a/pom.xml +++ b/pom.xml @@ -1637,6 +1637,17 @@ org.hsqldb hsqldb ${hsqldb.version} + + + org.testcontainers + postgresql + ${testcontainers.version} + test + + + org.testcontainers + jdbc + ${testcontainers.version} test From 076b1943fb974d4093275ff54fca4337b0ca88a6 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 8 Sep 2021 10:12:25 +0300 Subject: [PATCH 35/49] sql dao test: init ODBC connection to Postgres DB with custom PostgreSqlInitializer (class rule replacement, PostgreSQL dao tests able to run as standalone) --- .../dao/PostgreSqlDaoServiceTestSuite.java | 31 -------- .../server/dao/PostgreSqlInitializer.java | 79 +++++++++++++++++++ dao/src/test/resources/psql-test.properties | 4 +- 3 files changed, 81 insertions(+), 33 deletions(-) create mode 100644 dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java diff --git a/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java index 5d6f1a374f..915379fe74 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java @@ -30,13 +30,10 @@ */ package org.thingsboard.server.dao; -import org.junit.ClassRule; import org.junit.extensions.cpsuite.ClasspathSuite; import org.junit.extensions.cpsuite.ClasspathSuite.ClassnameFilters; import org.junit.runner.RunWith; -import java.util.Arrays; - @RunWith(ClasspathSuite.class) @ClassnameFilters({ "org.thingsboard.server.dao.service.psql.*SqlTest", @@ -45,32 +42,4 @@ import java.util.Arrays; "org.thingsboard.server.dao.service.timeseries.psql.*SqlTest" }) public class PostgreSqlDaoServiceTestSuite { - - @ClassRule - public static CustomSqlUnit sqlUnit = new CustomSqlUnit( - Arrays.asList("sql/schema-ts-psql.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql" - , "sql/system-data.sql" - , "sql/system-test-psql.sql" - ), - "sql/psql/drop-all-tables.sql", - "psql-test.properties" - ); - -// @ClassRule -// public static CustomSqlUnit sqlUnit = new CustomSqlUnit( -// Arrays.asList("sql/schema-ts-psql.sql" -// , "sql/schema-entities.sql", "sql/schema-entities-idx.sql" -// , "sql/system-data.sql", "sql/system-test.sql" -// ), -// "sql/psql/drop-all-tables.sql", -// "sql-test.properties" -// ); - -// @ClassRule -// public static CustomSqlUnit sqlUnit = new CustomSqlUnit( -// Arrays.asList("sql/schema-timescale.sql", "sql/schema-entities.sql", "sql/schema-entities-idx.sql", "sql/system-data.sql", "sql/system-test.sql"), -// "sql/timescale/drop-all-tables.sql", -// "sql-test.properties" -// ); - } 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..5f70b7ad44 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java @@ -0,0 +1,79 @@ +/** + * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * + * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * + * NOTICE: All information contained herein is, and remains + * the property of ThingsBoard, Inc. and its suppliers, + * if any. The intellectual and technical concepts contained + * herein are proprietary to ThingsBoard, Inc. + * and its suppliers and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * + * Dissemination of this information or reproduction of this material is strictly forbidden + * unless prior written permission is obtained from COMPANY. + * + * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, + * managers or contractors who have executed Confidentiality and Non-disclosure agreements + * explicitly covering such access. + * + * The copyright notice above does not evidence any actual or intended publication + * or disclosure of this source code, which includes + * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. + * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, + * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT + * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, + * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. + * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION + * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, + * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + */ +package org.thingsboard.server.dao; + +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/resources/psql-test.properties b/dao/src/test/resources/psql-test.properties index 00420be628..fb65966acf 100644 --- a/dao/src/test/resources/psql-test.properties +++ b/dao/src/test/resources/psql-test.properties @@ -12,10 +12,10 @@ 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 +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=20 +spring.datasource.hikari.maximumPoolSize=50 service.type=monolith #database.ts.type=timescale #database.ts.type=sql From 7585239339c7dc72f6a2bc3a377fe18c27fbaec0 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 8 Sep 2021 10:24:18 +0300 Subject: [PATCH 36/49] tests: cleanup after dubug --- .../dao/service/BaseEntityServiceTest.java | 110 +----------------- 1 file changed, 1 insertion(+), 109 deletions(-) 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 fbace01ddb..cd4763e4c0 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 @@ -21,7 +21,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomUtils; import org.hamcrest.Matchers; -import org.jetbrains.annotations.NotNull; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -83,7 +82,6 @@ import java.util.Comparator; import java.util.List; import java.util.Random; import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -582,121 +580,15 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { createRelation(tenantId, "SharedWithAsset0", assets.get(0).getId(), devices.get(n).getId()); } - //createManyCustomRelationsBetweenTwoNodes(tenantId, "UseCase", assets, devices); + 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()); - //testQuery(); - //printAllRelations(); } - private void testQuery() { - - template.query("" + - " 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" + - " );\n", r -> null); - - log.error("insert test {}", (Object) template.query("" + - "INSERT INTO relation_test (from_id, from_type, to_id, to_type, relation_type_group, relation_type, additional_info) " + - " VALUES " + - " ('11111111-0f19-11ec-ba23-e981fc95500d', 'TENANT', '22222222-0f19-11ec-ba23-e981fc95500d', 'ASSET', 'COMMON', 'Contains', null),\n" + - " ('22222222-0f19-11ec-ba23-e981fc95500d', 'ASSET', '33333333-0f19-11ec-ba23-e981fc95500d', 'DEVICE', 'COMMON', 'Contains', null),\n" + - " ('33333333-0f19-11ec-ba23-e981fc95500d', 'DEVICE', '11111111-0f19-11ec-ba23-e981fc95500d', 'TENANT', 'COMMON', 'Contains', null);\n" + - "" - , r -> null)); - - - //log.error("array_position hsql {}", template.queryForObject("select array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon') from relation_test", Long.class)); - - log.error("array_position hsql {}", template.queryForObject("select position_array('mon' in ARRAY['sun','mon','tue']);", Long.class)); - log.error("array_position psql {}", template.queryForObject("select array_position(ARRAY['sun','mon','tue'], 'mon');", Long.class)); - - Long countTest = template.queryForObject("select count(*) from relation_test", Long.class); - log.error("count test {}", countTest); - - Long count = template.queryForObject("select count(*) from relation", Long.class); - log.error("count {}", count); - - List> result = template.query("" + - "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 [from_id] as path " + - " FROM relation_test r " + - " WHERE from_id = '11111111-0f19-11ec-ba23-e981fc95500d' " + - " and from_type = 'TENANT' " + - " and relation_type_group = 'COMMON' " + - " GROUP BY r.from_id, r.from_type, r.to_id, r.to_type , 1, ARRAY [from_id] " + - " UNION ALL " + - " SELECT r.from_id, " + - " r.from_type, " + - " r.to_id, " + - " r.to_type, " + - " (re.lvl + 1) as lvl, " + - " (re.path || ARRAY [r.from_id]) as path " + - " FROM relation_test r " + - " INNER JOIN related_entities re " + - " ON r.from_id = re.to_id and " + - " r.from_type = re.to_type and " + - " relation_type_group = 'COMMON' and " + - " r.from_id NOT IN (SELECT * FROM unnest(re.path)) and " + - " re.lvl <= 7 " + - " GROUP BY r.from_id, r.from_type, r.to_id, r.to_type, " + - " (re.lvl + 1), (re.path || ARRAY [r.from_id])) " + - " " + - " SELECT lvl, from_id, from_type, to_id, to_type, path " + //to_id IN (SELECT * FROM unnest(path)) as is_present, - " from related_entities r_int " + - " " + - " ", - getListResultSetExtractor()); - - log.error("result {}", result.size() - 1); - AtomicInteger counter = new AtomicInteger(); - result.forEach(r -> System.out.printf("%s %s\n", counter.incrementAndGet(), r.toString())); - log.error("end"); - - log.error("query 1 (expected true): {}", template.queryForObject( - "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3'), UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')])) ", - String.class)); - log.error("query 1.1 (expected true): {}", template.queryForObject( - "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')] || ARRAY[UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')] )) ", - String.class)); - log.error("query 2 (expected true): {}", template.queryForObject( - "SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3') NOT IN (SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')) ", - String.class)); -// log.error("query true3: ", template.queryForObject( -// "SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3' IN (SELECT UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3')) ", -// String.class)); - log.error("query 3 (expected true): {} ", template.queryForObject( - "SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3' NOT IN (SELECT '463e5c80-0f38-11ec-8153-55a9f38b54f3') ", - String.class)); - - List> result2 = template.query("SELECT * FROM unnest(ARRAY[UUID('463e5c80-0f38-11ec-8153-55a9f38b54f3'), UUID('46957d30-0f38-11ec-8153-55a9f38b54f3')]) " - , getListResultSetExtractor()); - log.error("result2 {}", result2.size() - 1); - AtomicInteger counter2 = new AtomicInteger(); - result2.forEach(r -> System.out.printf("%s %s\n", counter2.incrementAndGet(), r.toString())); - log.error("end2"); - - } - - @NotNull private ResultSetExtractor>> getListResultSetExtractor() { return rs -> { List> list = new ArrayList<>(); From 2d2aa50520346ac2d302d1f334073e64db376171 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 8 Sep 2021 10:25:29 +0300 Subject: [PATCH 37/49] tests: added psql exclude in application pom. dao pom copied the same rules as fo rapplication --- application/pom.xml | 1 + dao/pom.xml | 6 ++++++ 2 files changed, 7 insertions(+) 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/dao/pom.xml b/dao/pom.xml index 1488d7eec5..2dd76cfcd4 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -254,7 +254,13 @@ maven-surefire-plugin ${surfire.version} + + **/sql/*Test.java + **/psql/*Test.java + **/nosql/*Test.java + + **/*Test.java **/*TestSuite.java From f4cfd92aaa265e7055d1fb634e58239889a5f6c0 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 8 Sep 2021 10:47:38 +0300 Subject: [PATCH 38/49] tests dao: marked base classes as abstract, exclude subfolders for sql test on in pom --- dao/pom.xml | 1 + .../server/dao/service/BaseDeviceProfileServiceTest.java | 2 +- .../server/dao/service/BaseOAuth2ConfigTemplateServiceTest.java | 2 +- .../thingsboard/server/dao/service/BaseOAuth2ServiceTest.java | 2 +- .../server/dao/service/BaseTenantProfileServiceTest.java | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/dao/pom.xml b/dao/pom.xml index 2dd76cfcd4..f490a0e8e5 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -256,6 +256,7 @@ **/sql/*Test.java + **/sql/*/*Test.java **/psql/*Test.java **/nosql/*Test.java 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/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<>(); From 8b6a9be476c76da0871c219ad96df48c59be2c03 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Fri, 10 Sep 2021 12:29:48 +0300 Subject: [PATCH 39/49] fixed re.lvl with r_int.lvl --- .../server/dao/sql/query/DefaultEntityQueryRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 22d2ac62f2..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 @@ -593,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 + ")"; @@ -672,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 + ")"; From 2d08573feeff00949305d616e4afdd5046e2dcc5 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Wed, 15 Sep 2021 11:00:45 +0300 Subject: [PATCH 40/49] relation query test: maxLevel and fetchLastLevelOnly cases added --- .../dao/service/BaseEntityServiceTest.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) 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 cd4763e4c0..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 @@ -92,6 +92,7 @@ import static org.hamcrest.MatcherAssert.assertThat; public abstract class BaseEntityServiceTest extends AbstractServiceTest { static final int ENTITY_COUNT = 5; + @Autowired private AttributesService attributesService; @@ -314,6 +315,20 @@ 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<>(); @@ -331,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 @@ -383,7 +400,6 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { deviceService.deleteDevicesByTenantId(tenantId); } - @Test public void testHierarchicalFindDevicesWithAttributesByQuery() throws ExecutionException, InterruptedException { List assets = new ArrayList<>(); @@ -403,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 From 319d47eaaded3376bef1b8126bf9d9a148d432b8 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Tue, 12 Oct 2021 18:48:28 +0300 Subject: [PATCH 41/49] fixed CE license headers --- .../dao/PostgreSqlDaoServiceTestSuite.java | 35 ++++++------------- .../server/dao/PostgreSqlInitializer.java | 35 ++++++------------- .../server/dao/service/DaoPostgreSqlTest.java | 35 ++++++------------- 3 files changed, 30 insertions(+), 75 deletions(-) diff --git a/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java index 915379fe74..b89c70e8f4 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlDaoServiceTestSuite.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.thingsboard.server.dao; diff --git a/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java index 5f70b7ad44..429fa1d711 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java +++ b/dao/src/test/java/org/thingsboard/server/dao/PostgreSqlInitializer.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.thingsboard.server.dao; 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 index 16da531369..43ed1f4d02 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/DaoPostgreSqlTest.java @@ -1,32 +1,17 @@ /** - * ThingsBoard, Inc. ("COMPANY") CONFIDENTIAL + * Copyright © 2016-2021 The Thingsboard Authors * - * Copyright © 2016-2021 ThingsBoard, Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * NOTICE: All information contained herein is, and remains - * the property of ThingsBoard, Inc. and its suppliers, - * if any. The intellectual and technical concepts contained - * herein are proprietary to ThingsBoard, Inc. - * and its suppliers and may be covered by U.S. and Foreign Patents, - * patents in process, and are protected by trade secret or copyright law. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Dissemination of this information or reproduction of this material is strictly forbidden - * unless prior written permission is obtained from COMPANY. - * - * Access to the source code contained herein is hereby forbidden to anyone except current COMPANY employees, - * managers or contractors who have executed Confidentiality and Non-disclosure agreements - * explicitly covering such access. - * - * The copyright notice above does not evidence any actual or intended publication - * or disclosure of this source code, which includes - * information that is confidential and/or proprietary, and is a trade secret, of COMPANY. - * ANY REPRODUCTION, MODIFICATION, DISTRIBUTION, PUBLIC PERFORMANCE, - * OR PUBLIC DISPLAY OF OR THROUGH USE OF THIS SOURCE CODE WITHOUT - * THE EXPRESS WRITTEN CONSENT OF COMPANY IS STRICTLY PROHIBITED, - * AND IN VIOLATION OF APPLICABLE LAWS AND INTERNATIONAL TREATIES. - * THE RECEIPT OR POSSESSION OF THIS SOURCE CODE AND/OR RELATED INFORMATION - * DOES NOT CONVEY OR IMPLY ANY RIGHTS TO REPRODUCE, DISCLOSE OR DISTRIBUTE ITS CONTENTS, - * OR TO MANUFACTURE, USE, OR SELL ANYTHING THAT IT MAY DESCRIBE, IN WHOLE OR IN PART. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.thingsboard.server.dao.service; From da2ff5e4ffba7274066b4267125badb99c0bd378 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Tue, 12 Oct 2021 18:55:29 +0300 Subject: [PATCH 42/49] Asset Controller Description --- .../server/controller/AssetController.java | 127 +++++++++++++++--- .../server/controller/BaseController.java | 14 +- .../server/controller/CustomerController.java | 2 +- .../server/controller/DeviceController.java | 13 +- .../server/common/data/asset/Asset.java | 30 +++++ .../server/common/data/asset/AssetInfo.java | 5 + .../common/data/asset/AssetSearchQuery.java | 4 + 7 files changed, 170 insertions(+), 25 deletions(-) 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..b4e95bc906 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,14 @@ public class AssetController extends BaseController { } } + @ApiOperation(value = "Create Or Update Asset (saveAsset)", + notes = "Creates or Updates the Asset. Platform generates random asset Id during asset creation. " + + "The asset id will be present in the response. " + + "Specify the asset id when you would like to update the asset. Referencing non-existing asset Id will cause an 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 +156,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.", produces = MediaType.APPLICATION_JSON_VALUE) @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 +185,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 +221,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 +257,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 +287,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 = 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 +319,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 = 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 +351,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 +368,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 = 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 +405,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 = 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 +442,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 +471,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 +499,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 +515,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 +552,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 +587,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 = 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 +642,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/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index ad54aaf526..a35f0fd995 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -162,19 +162,27 @@ public abstract class BaseController { 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 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 name."; - protected final String SORT_PROPERTY_DESCRIPTION = "Property of device to sort by"; + protected final String ASSET_TEXT_SEARCH_DESCRIPTION = "The case insensitive 'startsWith' filter based on the asset name."; + protected final String SORT_PROPERTY_DESCRIPTION = "Property of entity to sort by"; protected final String SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, label, type"; - protected final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESCENDING (DESC)"; + protected final String CUSTOMER_SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, title"; + 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"; @@ -920,7 +928,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/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index 28a40706b9..1d6146bcf8 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -198,7 +198,7 @@ public class CustomerController extends BaseController { @RequestParam int page, @ApiParam(value = CUSTOMER_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = SORT_PROPERTY_ALLOWABLE_VALUES) + @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 { 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 14c234c82f..529978058c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -120,8 +120,8 @@ 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) + "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 @@ -138,8 +138,8 @@ public class DeviceController extends BaseController { @ApiOperation(value = "Create Or Update Device (saveDevice)", notes = "Creates or Updates the Device. Platform generates random device Id and credentials (access token) during device creation. " + - "The device id will be present in the response. " + - "Specify the device id when you would like to update the device. Referencing non-existing device Id will cause an error.") + "The device id will be present in the response. " + + "Specify the device id when you would like to update the device. Referencing non-existing device Id will cause an error.") @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/device", method = RequestMethod.POST) @ResponseBody @@ -183,7 +183,7 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Delete device (deleteDevice)", - notes = "Deletes the device and it's credentials. Referencing non-existing device Id will cause an error.") + 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) @@ -438,12 +438,13 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Get Tenant Device (getTenantDevice)", - notes = "Requested device must be owned by tenant of customer that the user belongs to. " + + 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(); 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() { From 5822dc6e0b93620dacf5b36d5487826a4a25a044 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Tue, 12 Oct 2021 19:07:02 +0300 Subject: [PATCH 43/49] license header fix --- .../thingsboard/server/controller/DeviceController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index a774811e29..eb37344659 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -1,12 +1,12 @@ /** * Copyright © 2016-2021 The Thingsboard Authors - *

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

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

+ * + * http://www.apache.org/licenses/LICENSE-2.0 + * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. From 4510a3b37fc709526585bdfa2775930404752af0 Mon Sep 17 00:00:00 2001 From: Dima Landiak Date: Tue, 12 Oct 2021 19:18:35 +0300 Subject: [PATCH 44/49] removed unnecessary produces --- .../java/org/thingsboard/server/controller/AssetController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3b7598f092..732a2f72d2 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -158,7 +158,7 @@ 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.", produces = MediaType.APPLICATION_JSON_VALUE) + 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) From c50dd443c625acc380f8e9d4c1ae2f6ada21cf18 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 12 Oct 2021 19:29:10 +0300 Subject: [PATCH 45/49] Dashboard controller --- .../server/controller/BaseController.java | 10 +- .../server/controller/CustomerController.java | 14 +- .../controller/DashboardController.java | 202 +++++++++++++++--- .../server/controller/DeviceController.java | 38 ++-- .../controller/EntityViewController.java | 3 - .../server/controller/TenantController.java | 4 +- .../server/common/data/Dashboard.java | 5 + .../server/common/data/DashboardInfo.java | 25 +++ .../server/common/data/HomeDashboard.java | 6 + .../server/common/data/HomeDashboardInfo.java | 5 + .../server/common/data/ShortCustomerInfo.java | 6 + .../server/common/data/page/PageData.java | 8 +- 12 files changed, 263 insertions(+), 63 deletions(-) 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 ad54aaf526..d95ada2ce3 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -157,19 +157,25 @@ public abstract class BaseController { 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 CUSTOMER_ID = "customerId"; + public static final String TENANT_ID = "tenantId"; 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 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 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 device to sort by"; - protected final String SORT_PROPERTY_ALLOWABLE_VALUES = "createdTime, name, label, type"; + 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, label, type"; protected final String SORT_ORDER_DESCRIPTION = "Sort order. ASC (ASCENDING) or DESCENDING (DESC)"; 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. "; 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 28a40706b9..335140949a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -52,7 +52,6 @@ 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."; @@ -120,9 +119,10 @@ public class CustomerController extends BaseController { } @ApiOperation(value = "Create or update Customer (saveCustomer)", - notes = "Creates or Updates the Customer. Platform generates random Customer Id during device creation. " + - "The Customer Id will be present in the response. Specify the Customer Id when you would like to update the Customer. " + - "Referencing non-existing Customer Id will cause an error.") + 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 @@ -192,13 +192,13 @@ public class CustomerController extends BaseController { @RequestMapping(value = "/customers", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomers( - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @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 = SORT_PROPERTY_ALLOWABLE_VALUES) + @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 { 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 f8d50693a7..5647ba4b1d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java @@ -137,7 +137,7 @@ public class DeviceController extends BaseController { } @ApiOperation(value = "Create Or Update Device (saveDevice)", - notes = "Creates or Updates 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)" + + 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. " + @@ -376,15 +376,15 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/tenant/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantDevices( - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @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 = SORT_PROPERTY_ALLOWABLE_VALUES) + @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 { @@ -408,9 +408,9 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getTenantDeviceInfos( - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, @ApiParam(value = DEVICE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, @@ -418,7 +418,7 @@ public class DeviceController extends BaseController { @RequestParam(required = false) String deviceProfileId, @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = SORT_PROPERTY_ALLOWABLE_VALUES) + @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 @@ -462,17 +462,17 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/customer/{customerId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerDevices( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @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 = SORT_PROPERTY_ALLOWABLE_VALUES) + @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 { @@ -499,11 +499,11 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getCustomerDeviceInfos( - @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION) + @ApiParam(value = CUSTOMER_ID_PARAM_DESCRIPTION, required = true) @PathVariable("customerId") String strCustomerId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true) @RequestParam int page, @ApiParam(value = DEVICE_TYPE_DESCRIPTION) @RequestParam(required = false) String type, @@ -511,7 +511,7 @@ public class DeviceController extends BaseController { @RequestParam(required = false) String deviceProfileId, @ApiParam(value = DEVICE_TEXT_SEARCH_DESCRIPTION) @RequestParam(required = false) String textSearch, - @ApiParam(value = SORT_PROPERTY_DESCRIPTION, allowableValues = SORT_PROPERTY_ALLOWABLE_VALUES) + @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 { @@ -863,17 +863,17 @@ public class DeviceController extends BaseController { @RequestMapping(value = "/edge/{edgeId}/devices", params = {"pageSize", "page"}, method = RequestMethod.GET) @ResponseBody public PageData getEdgeDevices( - @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION) + @ApiParam(value = EDGE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(EDGE_ID) String strEdgeId, - @ApiParam(value = PAGE_SIZE_DESCRIPTION) + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true) @RequestParam int pageSize, - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) + @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 = SORT_PROPERTY_ALLOWABLE_VALUES) + @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, 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/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/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/page/PageData.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/PageData.java index 4b0ace9f68..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 @@ -48,22 +48,22 @@ public class PageData { this.hasNext = hasNext; } - @ApiModelProperty(position = 1, value = "Array of the entities.", readOnly = true) + @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) + @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) + @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) + @ApiModelProperty(position = 4, value = "'false' value indicates the end of the result set", readOnly = true) @JsonProperty("hasNext") public boolean hasNext() { return hasNext; From 708d0c684810ef78864a568868e2a5460c4eb7a6 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 16 Sep 2021 21:46:23 +0300 Subject: [PATCH 46/49] cpu usage reduced on TbNodeUtils --- .../rule/engine/api/util/TbNodeUtils.java | 16 +++++----- .../rule/engine/api/util/TbNodeUtilsTest.java | 30 +++++++++++++++++++ 2 files changed, 39 insertions(+), 7 deletions(-) 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))); + } } From 911712f09a16afa7a65db708da6ae663b254577b Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 30 Sep 2021 14:01:45 +0300 Subject: [PATCH 47/49] DeviceActorMessageProcessor: added debug log when "max concurrent sessions limit reached per device" (required to debug local integration that also fires that event) --- .../server/actors/device/DeviceActorMessageProcessor.java | 1 + 1 file changed, 1 insertion(+) 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!"); } From 177c0f46ad0171bd2c40f3fcb87806fc94b6c1d6 Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Sat, 25 Sep 2021 20:36:06 +0300 Subject: [PATCH 48/49] enqueueForTellNext for specific queue produce new message with the new queue from parameter. Instead old queue from old Msg. It affects rule node statistics before. This fix is related to Checkpoint node --- .../server/actors/ruleChain/DefaultTbContext.java | 10 +++++++--- .../java/org/thingsboard/server/common/msg/TbMsg.java | 5 +++-- 2 files changed, 10 insertions(+), 5 deletions(-) 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/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); } From fe511f080e7b3bd0e846833962cf81359299340b Mon Sep 17 00:00:00 2001 From: Sergey Matvienko Date: Thu, 16 Sep 2021 14:34:24 +0300 Subject: [PATCH 49/49] TbHttpClient for Rest API call node: implemented shared event loop for netty for any rest api call node instance --- .../rule/engine/rest/TbHttpClient.java | 12 +++- .../rule/engine/rest/TbRestApiCallNode.java | 2 +- .../rule/engine/rest/TbHttpClientTest.java | 61 +++++++++++++++++++ 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 76f10ebb0c..08eec6fd2a 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -78,7 +78,7 @@ public class TbHttpClient { private AsyncRestTemplate httpClient; private Deque>> 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/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