| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -33,11 +33,11 @@ import jakarta.annotation.PostConstruct;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import jakarta.annotation.PreDestroy;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import lombok.extern.slf4j.Slf4j;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.beans.factory.annotation.Autowired;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.beans.factory.annotation.Value;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.http.HttpStatus;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.http.ResponseEntity;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.security.access.prepost.PreAuthorize;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.web.bind.annotation.PathVariable;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.web.bind.annotation.PostMapping;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.web.bind.annotation.RequestBody;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.web.bind.annotation.RequestMapping;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.springframework.web.bind.annotation.RequestMethod;
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -65,25 +65,17 @@ import org.thingsboard.server.common.data.id.TenantId;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.id.UUIDBased;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.Aggregation;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.AttributeKvEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.BooleanDataEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.DataType;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.DoubleDataEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.IntervalType;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.JsonDataEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.KvEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.LongDataEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.StringDataEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.kv.TsKvEntry;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.common.msg.rule.engine.DeviceAttributesEventNotificationMsg;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.config.annotations.ApiOperation;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.dao.timeseries.TimeseriesService;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.exception.InvalidParametersException;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.exception.UncheckedApiException;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.queue.util.TbCoreComponent;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.service.security.AccessValidator;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				import org.thingsboard.server.service.security.model.SecurityUser;
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -156,9 +148,6 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @Autowired
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private TbTelemetryService tbTelemetryService;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @Value("${transport.json.max_string_value_length:0}")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private int maxStringValueLength;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private ExecutorService executor;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PostConstruct
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -314,10 +303,10 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = "A string value representing the timezone that will be used to calculate exact timestamps for 'WEEK', 'WEEK_ISO', 'MONTH' and 'QUARTER' interval types.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @RequestParam(name = "timeZone", required = false) String timeZone,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = "An integer value that represents a max number of time series data points to fetch." +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                     " This parameter is used only in the case if 'agg' parameter is set to 'NONE'.", schema = @Schema(defaultValue = "100"))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    " This parameter is used only in the case if 'agg' parameter is set to 'NONE'.", schema = @Schema(defaultValue = "100"))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @RequestParam(name = "limit", defaultValue = "100") Integer limit,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = "A string value representing the aggregation function. " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                     "If the interval is not specified, 'agg' parameter will use 'NONE' value.",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "If the interval is not specified, 'agg' parameter will use 'NONE' value.",
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    schema = @Schema(allowableValues = {"MIN", "MAX", "AVG", "SUM", "COUNT", "NONE"}))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @RequestParam(name = "agg", defaultValue = "NONE") String aggStr,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = SORT_ORDER_DESCRIPTION, schema = @Schema(allowableValues = {"ASC", "DESC"}))
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -337,20 +326,21 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @ApiResponses(value = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "200", description = SAVE_ATTIRIBUTES_STATUS_OK +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED', " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "and also sends event msg to the rule engine with msg type 'ATTRIBUTES_UPDATED'."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED', " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "and also sends event msg to the rule engine with msg type 'ATTRIBUTES_UPDATED'."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "400", description = SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "401", description = "User is not authorized to save device attributes for selected device. Most likely, User belongs to different Customer or Tenant."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "500", description = "The exception was thrown during processing the request. " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED' that includes an error stacktrace."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Platform creates an audit log event about device attributes updates with action type 'ATTRIBUTES_UPDATED' that includes an error stacktrace."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.POST)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @ResponseBody
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public DeferredResult<ResponseEntity> saveDeviceAttributes(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PostMapping(value = "/{deviceId}/{scope}")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public DeferredResult<ResponseEntity> saveDeviceAttributes(@Parameter(description = DEVICE_ID_PARAM_DESCRIPTION, required = true)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                               @PathVariable("deviceId") String deviceIdStr,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                               @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                               @PathVariable("scope") AttributeScope scope,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                               @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                               @RequestBody String request) throws ThingsboardException {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return saveAttributes(getTenantId(), entityId, scope, request);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -367,13 +357,15 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "500", description = SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.POST)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @ResponseBody
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public DeferredResult<ResponseEntity> saveEntityAttributesV1(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"})) @PathVariable("scope") AttributeScope scope,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PostMapping(value = "/{entityType}/{entityId}/{scope}")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public DeferredResult<ResponseEntity> saveEntityAttributesV1(@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE"))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @PathVariable("entityType") String entityType,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @PathVariable("entityId") String entityIdStr,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @PathVariable("scope") AttributeScope scope,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @RequestBody String request) throws ThingsboardException {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return saveAttributes(getTenantId(), entityId, scope, request);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -390,13 +382,15 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "500", description = SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @RequestMapping(value = "/{entityType}/{entityId}/attributes/{scope}", method = RequestMethod.POST)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @ResponseBody
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public DeferredResult<ResponseEntity> saveEntityAttributesV2(
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE")) @PathVariable("entityType") String entityType,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED)) @PathVariable("scope") AttributeScope scope,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PostMapping(value = "/{entityType}/{entityId}/attributes/{scope}")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public DeferredResult<ResponseEntity> saveEntityAttributesV2(@Parameter(description = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, schema = @Schema(defaultValue = "DEVICE"))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @PathVariable("entityType") String entityType,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @Parameter(description = ENTITY_ID_PARAM_DESCRIPTION, required = true)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @PathVariable("entityId") String entityIdStr,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @Parameter(description = ATTRIBUTES_SCOPE_DESCRIPTION, schema = @Schema(allowableValues = {"SERVER_SCOPE", "SHARED_SCOPE"}, requiredMode = Schema.RequiredMode.REQUIRED))
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @PathVariable("scope") AttributeScope scope,
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @io.swagger.v3.oas.annotations.parameters.RequestBody(description = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                                 @RequestBody String request) throws ThingsboardException {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return saveAttributes(getTenantId(), entityId, scope, request);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -460,11 +454,11 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @ApiResponses(value = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "200", description = "Time series for the selected keys in the request was removed. " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "Platform creates an audit log event about entity time series removal with action type 'TIMESERIES_DELETED'."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Platform creates an audit log event about entity time series removal with action type 'TIMESERIES_DELETED'."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "400", description = "Platform returns a bad request in case if keys list is empty or start and end timestamp values is empty when deleteAllDataForKeys is set to false."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "401", description = "User is not authorized to delete entity time series for selected entity. Most likely, User belongs to different Customer or Tenant."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "500", description = "The exception was thrown during processing the request. " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "Platform creates an audit log event about entity time series removal with action type 'TIMESERIES_DELETED' that includes an error stacktrace."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Platform creates an audit log event about entity time series removal with action type 'TIMESERIES_DELETED' that includes an error stacktrace."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE)
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -541,11 +535,11 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @ApiResponses(value = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "200", description = "Device attributes was removed for the selected keys in the request. " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED'."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED'."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "400", description = "Platform returns a bad request in case if keys or scope are not specified."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "401", description = "User is not authorized to delete device attributes for selected entity. Most likely, User belongs to different Customer or Tenant."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "500", description = "The exception was thrown during processing the request. " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED' that includes an error stacktrace."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Platform creates an audit log event about device attributes removal with action type 'ATTRIBUTES_DELETED' that includes an error stacktrace."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE)
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -563,11 +557,11 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @ApiResponses(value = {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "200", description = "Entity attributes was removed for the selected keys in the request. " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED'."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED'."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "400", description = "Platform returns a bad request in case if keys or scope are not specified."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "401", description = "User is not authorized to delete entity attributes for selected entity. Most likely, User belongs to different Customer or Tenant."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            @ApiResponse(responseCode = "500", description = "The exception was thrown during processing the request. " +
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                                                             "Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED' that includes an error stacktrace."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    "Platform creates an audit log event about entity attributes removal with action type 'ATTRIBUTES_DELETED' that includes an error stacktrace."),
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    })
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.DELETE)
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -616,18 +610,24 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private DeferredResult<ResponseEntity> saveAttributes(TenantId srcTenantId, EntityId entityIdSrc, AttributeScope scope, JsonNode json) throws ThingsboardException {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private DeferredResult<ResponseEntity> saveAttributes(TenantId srcTenantId, EntityId entityIdSrc, AttributeScope scope, String jsonStr) throws ThingsboardException {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (AttributeScope.SERVER_SCOPE != scope && AttributeScope.SHARED_SCOPE != scope) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return getImmediateDeferredResult("Invalid scope: " + scope, HttpStatus.BAD_REQUEST);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (json.isObject()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            List<AttributeKvEntry> attributes = extractRequestAttributes(json);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        JsonElement json;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        try {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            json = JsonParser.parseString(jsonStr);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } catch (Exception e) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return getImmediateDeferredResult("Invalid JSON", HttpStatus.BAD_REQUEST);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if (json.isJsonObject()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            List<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(json);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (attributes.isEmpty()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            for (AttributeKvEntry attributeKvEntry : attributes) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if (attributeKvEntry.getKey().isBlank()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    return getImmediateDeferredResult("Key cannot be blank", HttpStatus.BAD_REQUEST);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            SecurityUser user = getCurrentUser();
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				@ -885,43 +885,6 @@ public class TelemetryController extends BaseController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return result;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private List<AttributeKvEntry> extractRequestAttributes(JsonNode jsonNode) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        long ts = System.currentTimeMillis();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        List<AttributeKvEntry> attributes = new ArrayList<>();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        jsonNode.fields().forEachRemaining(entry -> {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            String key = entry.getKey();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            JsonNode value = entry.getValue();
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            if (entry.getValue().isObject() || entry.getValue().isArray()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                attributes.add(new BaseAttributeKvEntry(new JsonDataEntry(key, toJsonStr(value)), ts));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            } else if (entry.getValue().isTextual()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if (maxStringValueLength > 0 && entry.getValue().textValue().length() > maxStringValueLength) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    String message = String.format("String value length [%d] for key [%s] is greater than maximum allowed [%d]", entry.getValue().textValue().length(), key, maxStringValueLength);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    throw new UncheckedApiException(new InvalidParametersException(message));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                attributes.add(new BaseAttributeKvEntry(new StringDataEntry(key, value.textValue()), ts));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            } else if (entry.getValue().isBoolean()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                attributes.add(new BaseAttributeKvEntry(new BooleanDataEntry(key, value.booleanValue()), ts));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            } else if (entry.getValue().isDouble()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                attributes.add(new BaseAttributeKvEntry(new DoubleDataEntry(key, value.doubleValue()), ts));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            } else if (entry.getValue().isNumber()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                if (entry.getValue().isBigInteger()) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    throw new UncheckedApiException(new InvalidParametersException("Big integer values are not supported!"));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                } else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                    attributes.add(new BaseAttributeKvEntry(new LongDataEntry(key, value.longValue()), ts));
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        });
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        return attributes;
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private String toJsonStr(JsonNode value) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        try {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return JacksonUtil.toString(value);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        } catch (IllegalArgumentException e) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            throw new JsonParseException("Can't parse jsonValue: " + value, e);
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private JsonNode toJsonNode(String value) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        try {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return JacksonUtil.toJsonNode(value);
 | 
			
		
		
	
	
		
			
				
					
					| 
						
					 | 
				
			
			 | 
			 | 
			
				
 
 |