Commit 70fdc7d07bc45ef134af6ccf92b5f6353e7ffead

Authored by Igor Kulikov
2 parents 585de750 38a3ca68

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

@@ -232,9 +232,9 @@ public class AlarmController extends BaseController { @@ -232,9 +232,9 @@ public class AlarmController extends BaseController {
232 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET) 232 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
233 @ResponseBody 233 @ResponseBody
234 public PageData<AlarmInfo> getAlarms( 234 public PageData<AlarmInfo> getAlarms(
235 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) 235 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE")
236 @PathVariable(ENTITY_TYPE) String strEntityType, 236 @PathVariable(ENTITY_TYPE) String strEntityType,
237 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) 237 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
238 @PathVariable(ENTITY_ID) String strEntityId, 238 @PathVariable(ENTITY_ID) String strEntityId,
239 @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES) 239 @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)
240 @RequestParam(required = false) String searchStatus, 240 @RequestParam(required = false) String searchStatus,
@@ -333,7 +333,7 @@ public class AlarmController extends BaseController { @@ -333,7 +333,7 @@ public class AlarmController extends BaseController {
333 @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET) 333 @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET)
334 @ResponseBody 334 @ResponseBody
335 public AlarmSeverity getHighestAlarmSeverity( 335 public AlarmSeverity getHighestAlarmSeverity(
336 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true) 336 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE")
337 @PathVariable(ENTITY_TYPE) String strEntityType, 337 @PathVariable(ENTITY_TYPE) String strEntityType,
338 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) 338 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
339 @PathVariable(ENTITY_ID) String strEntityId, 339 @PathVariable(ENTITY_ID) String strEntityId,
@@ -156,13 +156,13 @@ public class AuditLogController extends BaseController { @@ -156,13 +156,13 @@ public class AuditLogController extends BaseController {
156 @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET) 156 @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
157 @ResponseBody 157 @ResponseBody
158 public PageData<AuditLog> getAuditLogsByEntityId( 158 public PageData<AuditLog> getAuditLogsByEntityId(
159 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) 159 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE")
160 @PathVariable("entityType") String strEntityType, 160 @PathVariable("entityType") String strEntityType,
161 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) 161 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
162 @PathVariable("entityId") String strEntityId, 162 @PathVariable("entityId") String strEntityId,
163 - @ApiParam(value = PAGE_SIZE_DESCRIPTION) 163 + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
164 @RequestParam int pageSize, 164 @RequestParam int pageSize,
165 - @ApiParam(value = PAGE_NUMBER_DESCRIPTION) 165 + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
166 @RequestParam int page, 166 @RequestParam int page,
167 @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION) 167 @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION)
168 @RequestParam(required = false) String textSearch, 168 @RequestParam(required = false) String textSearch,
@@ -1409,5 +1409,116 @@ public class ControllerConstants { @@ -1409,5 +1409,116 @@ public class ControllerConstants {
1409 "As a Tenant Administrator you are able to create multiple EVs per Device or Asset and assign them to different Customers. "; 1409 "As a Tenant Administrator you are able to create multiple EVs per Device or Asset and assign them to different Customers. ";
1410 protected static final String ENTITY_VIEW_INFO_DESCRIPTION = "Entity Views Info extends the Entity View with customer title and 'is public' flag. " + ENTITY_VIEW_DESCRIPTION; 1410 protected static final String ENTITY_VIEW_INFO_DESCRIPTION = "Entity Views Info extends the Entity View with customer title and 'is public' flag. " + ENTITY_VIEW_DESCRIPTION;
1411 1411
  1412 + protected static final String ATTRIBUTES_SCOPE_DESCRIPTION = "A string value representing the attributes scope. For example, 'SERVER_SCOPE'.";
  1413 + protected static final String ATTRIBUTES_KEYS_DESCRIPTION = "A string value representing the comma-separated list of attributes keys. For example, 'active,inactivityAlarmTime'.";
  1414 + protected static final String ATTRIBUTES_SCOPE_ALLOWED_VALUES = "SERVER_SCOPE, CLIENT_SCOPE, SHARED_SCOPE";
  1415 + protected static final String ATTRIBUTES_JSON_REQUEST_DESCRIPTION = "A string value representing the json object. For example, '{\"key\":\"value\"}'. See API call description for more details.";
1412 1416
  1417 + protected static final String TELEMETRY_KEYS_BASE_DESCRIPTION = "A string value representing the comma-separated list of telemetry keys.";
  1418 + protected static final String TELEMETRY_KEYS_DESCRIPTION = TELEMETRY_KEYS_BASE_DESCRIPTION + " If keys are not selected, the result will return all latest timeseries. For example, 'temperature,humidity'.";
  1419 + protected static final String TELEMETRY_SCOPE_DESCRIPTION = "Value is deprecated, reserved for backward compatibility and not used in the API call implementation. Specify any scope for compatibility";
  1420 + protected static final String TELEMETRY_JSON_REQUEST_DESCRIPTION = "A JSON with the telemetry values. See API call description for more details.";
  1421 +
  1422 +
  1423 + protected static final String STRICT_DATA_TYPES_DESCRIPTION = "Enables/disables conversion of telemetry values to strings. Conversion is enabled by default. Set parameter to 'true' in order to disable the conversion.";
  1424 + protected static final String INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION = "Referencing a non-existing entity Id or invalid entity type will cause an error. ";
  1425 +
  1426 + protected static final String SAVE_ATTIRIBUTES_STATUS_OK = "Attribute from the request was created or updated. ";
  1427 + protected static final String INVALID_STRUCTURE_OF_THE_REQUEST = "Invalid structure of the request";
  1428 + protected static final String SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST = INVALID_STRUCTURE_OF_THE_REQUEST + " or invalid attributes scope provided.";
  1429 + protected static final String SAVE_ENTITY_ATTRIBUTES_STATUS_OK = "Platform creates an audit log event about entity attributes updates with action type 'ATTRIBUTES_UPDATED', " +
  1430 + "and also sends event msg to the rule engine with msg type 'ATTRIBUTES_UPDATED'.";
  1431 + protected static final String SAVE_ENTITY_ATTRIBUTES_STATUS_UNAUTHORIZED = "User is not authorized to save entity attributes for selected entity. Most likely, User belongs to different Customer or Tenant.";
  1432 + protected static final String SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR = "The exception was thrown during processing the request. " +
  1433 + "Platform creates an audit log event about entity attributes updates with action type 'ATTRIBUTES_UPDATED' that includes an error stacktrace.";
  1434 + protected static final String SAVE_ENTITY_TIMESERIES_STATUS_OK = "Timeseries from the request was created or updated. " +
  1435 + "Platform creates an audit log event about entity timeseries updates with action type 'TIMESERIES_UPDATED'.";
  1436 + protected static final String SAVE_ENTITY_TIMESERIES_STATUS_UNAUTHORIZED = "User is not authorized to save entity timeseries for selected entity. Most likely, User belongs to different Customer or Tenant.";
  1437 + protected static final String SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR = "The exception was thrown during processing the request. " +
  1438 + "Platform creates an audit log event about entity timeseries updates with action type 'TIMESERIES_UPDATED' that includes an error stacktrace.";
  1439 +
  1440 + protected static final String ENTITY_ATTRIBUTE_SCOPES = " List of possible attribute scopes depends on the entity type: " +
  1441 + "\n\n * SERVER_SCOPE - supported for all entity types;" +
  1442 + "\n * CLIENT_SCOPE - supported for devices;" +
  1443 + "\n * SHARED_SCOPE - supported for devices. "+ "\n\n";
  1444 +
  1445 + protected static final String ATTRIBUTE_DATA_EXAMPLE = "[\n" +
  1446 + " {\"key\": \"stringAttributeKey\", \"value\": \"value\", \"lastUpdateTs\": 1609459200000},\n" +
  1447 + " {\"key\": \"booleanAttributeKey\", \"value\": false, \"lastUpdateTs\": 1609459200001},\n" +
  1448 + " {\"key\": \"doubleAttributeKey\", \"value\": 42.2, \"lastUpdateTs\": 1609459200002},\n" +
  1449 + " {\"key\": \"longKeyExample\", \"value\": 73, \"lastUpdateTs\": 1609459200003},\n" +
  1450 + " {\"key\": \"jsonKeyExample\",\n" +
  1451 + " \"value\": {\n" +
  1452 + " \"someNumber\": 42,\n" +
  1453 + " \"someArray\": [1,2,3],\n" +
  1454 + " \"someNestedObject\": {\"key\": \"value\"}\n" +
  1455 + " },\n" +
  1456 + " \"lastUpdateTs\": 1609459200004\n" +
  1457 + " }\n" +
  1458 + "]";
  1459 +
  1460 + protected static final String LATEST_TS_STRICT_DATA_EXAMPLE = "{\n" +
  1461 + " \"stringTsKey\": [{ \"value\": \"value\", \"ts\": 1609459200000}],\n" +
  1462 + " \"booleanTsKey\": [{ \"value\": false, \"ts\": 1609459200000}],\n" +
  1463 + " \"doubleTsKey\": [{ \"value\": 42.2, \"ts\": 1609459200000}],\n" +
  1464 + " \"longTsKey\": [{ \"value\": 73, \"ts\": 1609459200000}],\n" +
  1465 + " \"jsonTsKey\": [{ \n" +
  1466 + " \"value\": {\n" +
  1467 + " \"someNumber\": 42,\n" +
  1468 + " \"someArray\": [1,2,3],\n" +
  1469 + " \"someNestedObject\": {\"key\": \"value\"}\n" +
  1470 + " }, \n" +
  1471 + " \"ts\": 1609459200000}]\n" +
  1472 + "}\n";
  1473 +
  1474 + protected static final String LATEST_TS_NON_STRICT_DATA_EXAMPLE = "{\n" +
  1475 + " \"stringTsKey\": [{ \"value\": \"value\", \"ts\": 1609459200000}],\n" +
  1476 + " \"booleanTsKey\": [{ \"value\": \"false\", \"ts\": 1609459200000}],\n" +
  1477 + " \"doubleTsKey\": [{ \"value\": \"42.2\", \"ts\": 1609459200000}],\n" +
  1478 + " \"longTsKey\": [{ \"value\": \"73\", \"ts\": 1609459200000}],\n" +
  1479 + " \"jsonTsKey\": [{ \"value\": \"{\\\"someNumber\\\": 42,\\\"someArray\\\": [1,2,3],\\\"someNestedObject\\\": {\\\"key\\\": \\\"value\\\"}}\", \"ts\": 1609459200000}]\n" +
  1480 + "}\n";
  1481 +
  1482 + protected static final String TS_STRICT_DATA_EXAMPLE = "{\n" +
  1483 + " \"temperature\": [\n" +
  1484 + " {\n" +
  1485 + " \"value\": 36.7,\n" +
  1486 + " \"ts\": 1609459200000\n" +
  1487 + " },\n" +
  1488 + " {\n" +
  1489 + " \"value\": 36.6,\n" +
  1490 + " \"ts\": 1609459201000\n" +
  1491 + " }\n" +
  1492 + " ]\n" +
  1493 + "}";
  1494 +
  1495 + protected static final String SAVE_ATTRIBUTES_REQUEST_PAYLOAD = "The request payload is a JSON object with key-value format of attributes to create or update. " +
  1496 + "For example:\n\n"
  1497 + + MARKDOWN_CODE_BLOCK_START
  1498 + + "{\n" +
  1499 + " \"stringKey\":\"value1\", \n" +
  1500 + " \"booleanKey\":true, \n" +
  1501 + " \"doubleKey\":42.0, \n" +
  1502 + " \"longKey\":73, \n" +
  1503 + " \"jsonKey\": {\n" +
  1504 + " \"someNumber\": 42,\n" +
  1505 + " \"someArray\": [1,2,3],\n" +
  1506 + " \"someNestedObject\": {\"key\": \"value\"}\n" +
  1507 + " }\n" +
  1508 + "}"
  1509 + + MARKDOWN_CODE_BLOCK_END + "\n";
  1510 +
  1511 + protected static final String SAVE_TIMESERIES_REQUEST_PAYLOAD = "The request payload is a JSON document with three possible formats:\n\n" +
  1512 + "Simple format without timestamp. In such a case, current server time will be used: \n\n" +
  1513 + MARKDOWN_CODE_BLOCK_START +
  1514 + "{\"temperature\": 26}" +
  1515 + MARKDOWN_CODE_BLOCK_END +
  1516 + "\n\n Single JSON object with timestamp: \n\n" +
  1517 + MARKDOWN_CODE_BLOCK_START +
  1518 + "{\"ts\":1634712287000,\"values\":{\"temperature\":26, \"humidity\":87}}" +
  1519 + MARKDOWN_CODE_BLOCK_END +
  1520 + "\n\n JSON array with timestamps: \n\n" +
  1521 + MARKDOWN_CODE_BLOCK_START +
  1522 + "[{\"ts\":1634712287000,\"values\":{\"temperature\":26, \"humidity\":87}}, {\"ts\":1634712588000,\"values\":{\"temperature\":25, \"humidity\":88}}]" +
  1523 + MARKDOWN_CODE_BLOCK_END ;
1413 } 1524 }
@@ -218,6 +218,7 @@ public class DeviceController extends BaseController { @@ -218,6 +218,7 @@ public class DeviceController extends BaseController {
218 @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException { 218 @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException {
219 Device device = checkNotNull(deviceAndCredentials.getDevice()); 219 Device device = checkNotNull(deviceAndCredentials.getDevice());
220 DeviceCredentials credentials = checkNotNull(deviceAndCredentials.getCredentials()); 220 DeviceCredentials credentials = checkNotNull(deviceAndCredentials.getCredentials());
  221 + boolean created = device.getId() == null;
221 try { 222 try {
222 device.setTenantId(getCurrentUser().getTenantId()); 223 device.setTenantId(getCurrentUser().getTenantId());
223 checkEntity(device.getId(), device, Resource.DEVICE); 224 checkEntity(device.getId(), device, Resource.DEVICE);
@@ -231,7 +232,7 @@ public class DeviceController extends BaseController { @@ -231,7 +232,7 @@ public class DeviceController extends BaseController {
231 return savedDevice; 232 return savedDevice;
232 } catch (Exception e) { 233 } catch (Exception e) {
233 logEntityAction(emptyId(EntityType.DEVICE), device, 234 logEntityAction(emptyId(EntityType.DEVICE), device,
234 - null, device.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e); 235 + null, created ? ActionType.ADDED : ActionType.UPDATED, e);
235 throw handleException(e); 236 throw handleException(e);
236 } 237 }
237 } 238 }
@@ -15,9 +15,7 @@ @@ -15,9 +15,7 @@
15 */ 15 */
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
18 -import com.fasterxml.jackson.core.JsonProcessingException;  
19 import com.fasterxml.jackson.databind.JsonNode; 18 import com.fasterxml.jackson.databind.JsonNode;
20 -import com.fasterxml.jackson.databind.ObjectMapper;  
21 import com.google.common.base.Function; 19 import com.google.common.base.Function;
22 import com.google.common.util.concurrent.FutureCallback; 20 import com.google.common.util.concurrent.FutureCallback;
23 import com.google.common.util.concurrent.Futures; 21 import com.google.common.util.concurrent.Futures;
@@ -46,6 +44,7 @@ import org.springframework.web.bind.annotation.RequestParam; @@ -46,6 +44,7 @@ import org.springframework.web.bind.annotation.RequestParam;
46 import org.springframework.web.bind.annotation.ResponseBody; 44 import org.springframework.web.bind.annotation.ResponseBody;
47 import org.springframework.web.bind.annotation.RestController; 45 import org.springframework.web.bind.annotation.RestController;
48 import org.springframework.web.context.request.async.DeferredResult; 46 import org.springframework.web.context.request.async.DeferredResult;
  47 +import org.thingsboard.common.util.JacksonUtil;
49 import org.thingsboard.common.util.ThingsBoardThreadFactory; 48 import org.thingsboard.common.util.ThingsBoardThreadFactory;
50 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; 49 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
51 import org.thingsboard.server.common.data.DataConstants; 50 import org.thingsboard.server.common.data.DataConstants;
@@ -90,7 +89,6 @@ import org.thingsboard.server.service.telemetry.exception.UncheckedApiException; @@ -90,7 +89,6 @@ import org.thingsboard.server.service.telemetry.exception.UncheckedApiException;
90 import javax.annotation.Nullable; 89 import javax.annotation.Nullable;
91 import javax.annotation.PostConstruct; 90 import javax.annotation.PostConstruct;
92 import javax.annotation.PreDestroy; 91 import javax.annotation.PreDestroy;
93 -import java.io.IOException;  
94 import java.util.ArrayList; 92 import java.util.ArrayList;
95 import java.util.Arrays; 93 import java.util.Arrays;
96 import java.util.HashSet; 94 import java.util.HashSet;
@@ -103,12 +101,41 @@ import java.util.concurrent.Executors; @@ -103,12 +101,41 @@ import java.util.concurrent.Executors;
103 import java.util.concurrent.TimeUnit; 101 import java.util.concurrent.TimeUnit;
104 import java.util.stream.Collectors; 102 import java.util.stream.Collectors;
105 103
  104 +import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_JSON_REQUEST_DESCRIPTION;
  105 +import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_KEYS_DESCRIPTION;
  106 +import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SCOPE_ALLOWED_VALUES;
  107 +import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTES_SCOPE_DESCRIPTION;
  108 +import static org.thingsboard.server.controller.ControllerConstants.ATTRIBUTE_DATA_EXAMPLE;
  109 +import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID;
106 import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION; 110 import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION;
  111 +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ATTRIBUTE_SCOPES;
107 import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION; 112 import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
108 import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION; 113 import static org.thingsboard.server.controller.ControllerConstants.ENTITY_TYPE_PARAM_DESCRIPTION;
  114 +import static org.thingsboard.server.controller.ControllerConstants.INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION;
  115 +import static org.thingsboard.server.controller.ControllerConstants.INVALID_STRUCTURE_OF_THE_REQUEST;
  116 +import static org.thingsboard.server.controller.ControllerConstants.LATEST_TS_NON_STRICT_DATA_EXAMPLE;
  117 +import static org.thingsboard.server.controller.ControllerConstants.LATEST_TS_STRICT_DATA_EXAMPLE;
  118 +import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_END;
  119 +import static org.thingsboard.server.controller.ControllerConstants.MARKDOWN_CODE_BLOCK_START;
  120 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST;
  121 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ATTIRIBUTES_STATUS_OK;
  122 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ATTRIBUTES_REQUEST_PAYLOAD;
  123 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR;
  124 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_ATTRIBUTES_STATUS_OK;
  125 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_ATTRIBUTES_STATUS_UNAUTHORIZED;
  126 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR;
  127 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_TIMESERIES_STATUS_OK;
  128 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_ENTITY_TIMESERIES_STATUS_UNAUTHORIZED;
  129 +import static org.thingsboard.server.controller.ControllerConstants.SAVE_TIMESERIES_REQUEST_PAYLOAD;
109 import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES; 130 import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
110 import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION; 131 import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_DESCRIPTION;
  132 +import static org.thingsboard.server.controller.ControllerConstants.STRICT_DATA_TYPES_DESCRIPTION;
  133 +import static org.thingsboard.server.controller.ControllerConstants.TELEMETRY_JSON_REQUEST_DESCRIPTION;
  134 +import static org.thingsboard.server.controller.ControllerConstants.TELEMETRY_KEYS_BASE_DESCRIPTION;
  135 +import static org.thingsboard.server.controller.ControllerConstants.TELEMETRY_KEYS_DESCRIPTION;
  136 +import static org.thingsboard.server.controller.ControllerConstants.TELEMETRY_SCOPE_DESCRIPTION;
111 import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; 137 import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH;
  138 +import static org.thingsboard.server.controller.ControllerConstants.TS_STRICT_DATA_EXAMPLE;
112 139
113 140
114 /** 141 /**
@@ -120,48 +147,6 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CU @@ -120,48 +147,6 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CU
120 @Slf4j 147 @Slf4j
121 public class TelemetryController extends BaseController { 148 public class TelemetryController extends BaseController {
122 149
123 - private static final String ATTRIBUTES_SCOPE_DESCRIPTION = "A string value representing the attributes scope. For example, 'SERVER_SCOPE'.";  
124 - private static final String ATTRIBUTES_KEYS_DESCRIPTION = "A string value representing the comma-separated list of attributes keys. For example, 'active,inactivityAlarmTime'.";  
125 - private static final String ATTRIBUTES_SCOPE_ALLOWED_VALUES = "SERVER_SCOPE, CLIENT_SCOPE, SHARED_SCOPE";  
126 - private static final String ATTRIBUTES_JSON_REQUEST_DESCRIPTION = "A string value representing the json object. For example, '{\"key\":\"value\"}'";  
127 - private static final String ATTRIBUTE_DATA_CLASS_DESCRIPTION = "AttributeData class represents information regarding a particular attribute and includes the next parameters: 'lastUpdatesTs' - a long value representing the timestamp of the last attribute modification in milliseconds. 'key' - attribute key name, and 'value' - attribute value.";  
128 - private static final String GET_ALL_ATTRIBUTES_BASE_DESCRIPTION = "Returns a JSON structure that represents a list of AttributeData class objects for the selected entity based on the specified comma-separated list of attribute key names. " + ATTRIBUTE_DATA_CLASS_DESCRIPTION;  
129 - private static final String GET_ALL_ATTRIBUTES_BY_SCOPE_BASE_DESCRIPTION = "Returns a JSON structure that represents a list of AttributeData class objects for the selected entity based on the attributes scope selected and a comma-separated list of attribute key names. " + ATTRIBUTE_DATA_CLASS_DESCRIPTION;  
130 -  
131 - private static final String TS_DATA_CLASS_DESCRIPTION = "TsData class is a timeseries data point for specific telemetry key that includes 'value' - object value, and 'ts' - a long value representing timestamp in milliseconds for this value. ";  
132 -  
133 - private static final String TELEMETRY_KEYS_BASE_DESCRIPTION = "A string value representing the comma-separated list of telemetry keys.";  
134 - private static final String TELEMETRY_KEYS_DESCRIPTION = TELEMETRY_KEYS_BASE_DESCRIPTION + " If keys are not selected, the result will return all latest timeseries. For example, 'temp,humidity'.";  
135 - private static final String TELEMETRY_SCOPE_DESCRIPTION = "Value is not used in the API call implementation. However, you need to specify whatever value cause scope is a path variable.";  
136 - private static final String TELEMETRY_JSON_REQUEST_DESCRIPTION = "A string value representing the json object. For example, '{\"key\":\"value\"}' or '{\"ts\":1527863043000,\"values\":{\"key1\":\"value1\",\"key2\":\"value2\"}}' or [{\"ts\":1527863043000,\"values\":{\"key1\":\"value1\",\"key2\":\"value2\"}}, {\"ts\":1527863053000,\"values\":{\"key1\":\"value3\",\"key2\":\"value4\"}}]";  
137 -  
138 -  
139 - private static final String STRICT_DATA_TYPES_DESCRIPTION = "A boolean value to specify if values of selected telemetry keys will represent string values(by default) or use strict data type.";  
140 - private static final String INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION = "Referencing a non-existing entity Id or invalid entity type will cause an error. ";  
141 -  
142 - private static final String SAVE_ENTITY_ATTRIBUTES_DESCRIPTION = "Creates or updates the entity attributes based on entity id, entity type, specified attributes scope " +  
143 - "and request payload that represents a JSON object with key-value format of attributes to create or update. " +  
144 - "For example, '{\"temperature\": 26}'. Key is a unique parameter and cannot be overwritten. Only value can be overwritten for the key. ";  
145 - private static final String SAVE_ATTIRIBUTES_STATUS_OK = "Attribute from the request was created or updated. ";  
146 - private static final String INVALID_STRUCTURE_OF_THE_REQUEST = "Invalid structure of the request";  
147 - private static final String SAVE_ATTIRIBUTES_STATUS_BAD_REQUEST = INVALID_STRUCTURE_OF_THE_REQUEST + " or invalid attributes scope provided.";  
148 - private static final String SAVE_ENTITY_ATTRIBUTES_STATUS_OK = "Platform creates an audit log event about entity attributes updates with action type 'ATTRIBUTES_UPDATED', " +  
149 - "and also sends event msg to the rule engine with msg type 'ATTRIBUTES_UPDATED'.";  
150 - private static final String SAVE_ENTITY_ATTRIBUTES_STATUS_UNAUTHORIZED = "User is not authorized to save entity attributes for selected entity. Most likely, User belongs to different Customer or Tenant.";  
151 - private static final String SAVE_ENTITY_ATTRIBUTES_STATUS_INTERNAL_SERVER_ERROR = "The exception was thrown during processing the request. " +  
152 - "Platform creates an audit log event about entity attributes updates with action type 'ATTRIBUTES_UPDATED' that includes an error stacktrace.";  
153 - private static final String SAVE_ENTITY_TIMESERIES_DESCRIPTION = "Creates or updates the entity timeseries based on entity id, entity type " +  
154 - "and request payload that represents a JSON object with key-value or ts-values format. " +  
155 - "For example, '{\"temperature\": 26}' or '{\"ts\":1634712287000,\"values\":{\"temperature\":26, \"humidity\":87}}', " +  
156 - "or JSON array with inner objects inside of ts-values format. " +  
157 - "For example, '[{\"ts\":1634712287000,\"values\":{\"temperature\":26, \"humidity\":87}}, {\"ts\":1634712588000,\"values\":{\"temperature\":25, \"humidity\":88}}]'. " +  
158 - "The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. ";  
159 - private static final String SAVE_ENTITY_TIMESERIES_STATUS_OK = "Timeseries from the request was created or updated. " +  
160 - "Platform creates an audit log event about entity timeseries updates with action type 'TIMESERIES_UPDATED'.";  
161 - private static final String SAVE_ENTITY_TIMESERIES_STATUS_UNAUTHORIZED = "User is not authorized to save entity timeseries for selected entity. Most likely, User belongs to different Customer or Tenant.";  
162 - private static final String SAVE_ENTITY_TIMESERIES_STATUS_INTERNAL_SERVER_ERROR = "The exception was thrown during processing the request. " +  
163 - "Platform creates an audit log event about entity timeseries updates with action type 'TIMESERIES_UPDATED' that includes an error stacktrace.";  
164 -  
165 @Autowired 150 @Autowired
166 private TimeseriesService tsService; 151 private TimeseriesService tsService;
167 152
@@ -173,8 +158,6 @@ public class TelemetryController extends BaseController { @@ -173,8 +158,6 @@ public class TelemetryController extends BaseController {
173 158
174 private ExecutorService executor; 159 private ExecutorService executor;
175 160
176 - private static final ObjectMapper mapper = new ObjectMapper();  
177 -  
178 @PostConstruct 161 @PostConstruct
179 public void initExecutor() { 162 public void initExecutor() {
180 executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("telemetry-controller")); 163 executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("telemetry-controller"));
@@ -188,96 +171,117 @@ public class TelemetryController extends BaseController { @@ -188,96 +171,117 @@ public class TelemetryController extends BaseController {
188 } 171 }
189 172
190 @ApiOperation(value = "Get all attribute keys (getAttributeKeys)", 173 @ApiOperation(value = "Get all attribute keys (getAttributeKeys)",
191 - notes = "Returns a list of all attribute key names for the selected entity. " +  
192 - "In the case of device entity specified, a response will include merged attribute key names list from each scope: " +  
193 - "SERVER_SCOPE, CLIENT_SCOPE, SHARED_SCOPE. "  
194 - + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 174 + notes = "Returns a set of unique attribute key names for the selected entity. " +
  175 + "The response will include merged key names set for all attribute scopes:" +
  176 + "\n\n * SERVER_SCOPE - supported for all entity types;" +
  177 + "\n * CLIENT_SCOPE - supported for devices;" +
  178 + "\n * SHARED_SCOPE - supported for devices. "
  179 + + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
195 produces = MediaType.APPLICATION_JSON_VALUE) 180 produces = MediaType.APPLICATION_JSON_VALUE)
196 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 181 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
197 @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes", method = RequestMethod.GET) 182 @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes", method = RequestMethod.GET)
198 @ResponseBody 183 @ResponseBody
199 public DeferredResult<ResponseEntity> getAttributeKeys( 184 public DeferredResult<ResponseEntity> getAttributeKeys(
200 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
201 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { 185 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  186 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
202 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback); 187 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, this::getAttributeKeysCallback);
203 } 188 }
204 189
205 - @ApiOperation(value = "Get all attributes keys by scope (getAttributeKeysByScope)",  
206 - notes = "Returns a list of attribute key names from the specified attributes scope for the selected entity. " +  
207 - "If scope parameter is omitted, Get all attribute keys(getAttributeKeys) API will be called. "  
208 - + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 190 + @ApiOperation(value = "Get all attribute keys by scope (getAttributeKeysByScope)",
  191 + notes = "Returns a set of unique attribute key names for the selected entity and attributes scope: " +
  192 + "\n\n * SERVER_SCOPE - supported for all entity types;" +
  193 + "\n * CLIENT_SCOPE - supported for devices;" +
  194 + "\n * SHARED_SCOPE - supported for devices. "
  195 + + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
209 produces = MediaType.APPLICATION_JSON_VALUE) 196 produces = MediaType.APPLICATION_JSON_VALUE)
210 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 197 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
211 @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes/{scope}", method = RequestMethod.GET) 198 @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes/{scope}", method = RequestMethod.GET)
212 @ResponseBody 199 @ResponseBody
213 public DeferredResult<ResponseEntity> getAttributeKeysByScope( 200 public DeferredResult<ResponseEntity> getAttributeKeysByScope(
214 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
215 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr,  
216 - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException { 201 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  202 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
  203 + @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope) throws ThingsboardException {
217 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, 204 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
218 (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope)); 205 (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope));
219 } 206 }
220 207
221 @ApiOperation(value = "Get attributes (getAttributes)", 208 @ApiOperation(value = "Get attributes (getAttributes)",
222 - notes = GET_ALL_ATTRIBUTES_BASE_DESCRIPTION + " If 'keys' parameter is omitted, AttributeData class objects will be added to the response for all existing keys of the selected entity. " +  
223 - INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 209 + notes = "Returns all attributes that belong to specified entity. Use optional 'keys' parameter to return specific attributes."
  210 + + "\n Example of the result: \n\n"
  211 + + MARKDOWN_CODE_BLOCK_START
  212 + + ATTRIBUTE_DATA_EXAMPLE
  213 + + MARKDOWN_CODE_BLOCK_END
  214 + + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
224 produces = MediaType.APPLICATION_JSON_VALUE) 215 produces = MediaType.APPLICATION_JSON_VALUE)
225 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 216 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
226 @RequestMapping(value = "/{entityType}/{entityId}/values/attributes", method = RequestMethod.GET) 217 @RequestMapping(value = "/{entityType}/{entityId}/values/attributes", method = RequestMethod.GET)
227 @ResponseBody 218 @ResponseBody
228 public DeferredResult<ResponseEntity> getAttributes( 219 public DeferredResult<ResponseEntity> getAttributes(
229 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
230 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr, 220 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  221 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
231 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { 222 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
232 SecurityUser user = getCurrentUser(); 223 SecurityUser user = getCurrentUser();
233 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, 224 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
234 (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr)); 225 (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
235 } 226 }
236 227
  228 +
237 @ApiOperation(value = "Get attributes by scope (getAttributesByScope)", 229 @ApiOperation(value = "Get attributes by scope (getAttributesByScope)",
238 - notes = GET_ALL_ATTRIBUTES_BY_SCOPE_BASE_DESCRIPTION + " In case that 'keys' parameter is not selected, " +  
239 - "AttributeData class objects will be added to the response for all existing attribute keys from the " +  
240 - "specified attributes scope of the selected entity. If 'scope' parameter is omitted, " +  
241 - "Get attributes (getAttributes) API will be called. " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 230 + notes = "Returns all attributes of a specified scope that belong to specified entity." +
  231 + ENTITY_ATTRIBUTE_SCOPES +
  232 + "Use optional 'keys' parameter to return specific attributes."
  233 + + "\n Example of the result: \n\n"
  234 + + MARKDOWN_CODE_BLOCK_START
  235 + + ATTRIBUTE_DATA_EXAMPLE
  236 + + MARKDOWN_CODE_BLOCK_END
  237 + + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
242 produces = MediaType.APPLICATION_JSON_VALUE) 238 produces = MediaType.APPLICATION_JSON_VALUE)
243 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 239 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
244 @RequestMapping(value = "/{entityType}/{entityId}/values/attributes/{scope}", method = RequestMethod.GET) 240 @RequestMapping(value = "/{entityType}/{entityId}/values/attributes/{scope}", method = RequestMethod.GET)
245 @ResponseBody 241 @ResponseBody
246 public DeferredResult<ResponseEntity> getAttributesByScope( 242 public DeferredResult<ResponseEntity> getAttributesByScope(
247 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
248 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr,  
249 - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, 243 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  244 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
  245 + @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
250 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException { 246 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
251 SecurityUser user = getCurrentUser(); 247 SecurityUser user = getCurrentUser();
252 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr, 248 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
253 (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr)); 249 (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, scope, keysStr));
254 } 250 }
255 251
256 - @ApiOperation(value = "Get timeseries keys (getTimeseriesKeys)",  
257 - notes = "Returns a list of all telemetry key names for the selected entity based on entity id and entity type specified. " +  
258 - INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 252 + @ApiOperation(value = "Get time-series keys (getTimeseriesKeys)",
  253 + notes = "Returns a set of unique time-series key names for the selected entity. " +
  254 + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
259 produces = MediaType.APPLICATION_JSON_VALUE) 255 produces = MediaType.APPLICATION_JSON_VALUE)
260 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 256 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
261 @RequestMapping(value = "/{entityType}/{entityId}/keys/timeseries", method = RequestMethod.GET) 257 @RequestMapping(value = "/{entityType}/{entityId}/keys/timeseries", method = RequestMethod.GET)
262 @ResponseBody 258 @ResponseBody
263 public DeferredResult<ResponseEntity> getTimeseriesKeys( 259 public DeferredResult<ResponseEntity> getTimeseriesKeys(
264 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
265 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr) throws ThingsboardException { 260 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  261 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr) throws ThingsboardException {
266 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr, 262 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
267 (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor())); 263 (result, tenantId, entityId) -> Futures.addCallback(tsService.findAllLatest(tenantId, entityId), getTsKeysToResponseCallback(result), MoreExecutors.directExecutor()));
268 } 264 }
269 265
270 - @ApiOperation(value = "Get latest timeseries (getLatestTimeseries)",  
271 - notes = "Returns a JSON structure that represents a Map, where the map key is a telemetry key name " +  
272 - "and map value - is a singleton list of TsData class objects. "  
273 - + TS_DATA_CLASS_DESCRIPTION + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 266 + @ApiOperation(value = "Get latest time-series value (getLatestTimeseries)",
  267 + notes = "Returns all time-series that belong to specified entity. Use optional 'keys' parameter to return specific time-series." +
  268 + " The result is a JSON object. The format of the values depends on the 'useStrictDataTypes' parameter." +
  269 + " By default, all time-series values are converted to strings: \n\n"
  270 + + MARKDOWN_CODE_BLOCK_START
  271 + + LATEST_TS_NON_STRICT_DATA_EXAMPLE
  272 + + MARKDOWN_CODE_BLOCK_END
  273 + + "\n\n However, it is possible to request the values without conversion ('useStrictDataTypes'=true): \n\n"
  274 + + MARKDOWN_CODE_BLOCK_START
  275 + + LATEST_TS_STRICT_DATA_EXAMPLE
  276 + + MARKDOWN_CODE_BLOCK_END
  277 + + "\n\n " + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
274 produces = MediaType.APPLICATION_JSON_VALUE) 278 produces = MediaType.APPLICATION_JSON_VALUE)
275 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 279 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
276 @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET) 280 @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET)
277 @ResponseBody 281 @ResponseBody
278 public DeferredResult<ResponseEntity> getLatestTimeseries( 282 public DeferredResult<ResponseEntity> getLatestTimeseries(
279 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
280 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr, 283 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  284 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
281 @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr, 285 @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr,
282 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION) 286 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION)
283 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException { 287 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
@@ -286,24 +290,26 @@ public class TelemetryController extends BaseController { @@ -286,24 +290,26 @@ public class TelemetryController extends BaseController {
286 (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes)); 290 (result, tenantId, entityId) -> getLatestTimeseriesValuesCallback(result, user, entityId, keysStr, useStrictDataTypes));
287 } 291 }
288 292
289 - @ApiOperation(value = "Get timeseries (getTimeseries)",  
290 - notes = "Returns a JSON structure that represents a Map, where the map key is a telemetry key name " +  
291 - "and map value - is a list of TsData class objects. " + TS_DATA_CLASS_DESCRIPTION +  
292 - "This method allows us to group original data into intervals and aggregate it using one of the aggregation methods or just limit the number of TsData objects to fetch for each key specified. " +  
293 - "See the desription of the request parameters for more details. " +  
294 - "The result can also be sorted in ascending or descending order. "  
295 - + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 293 + @ApiOperation(value = "Get time-series data (getTimeseries)",
  294 + notes = "Returns a range of time-series values for specified entity. " +
  295 + "Returns not aggregated data by default. " +
  296 + "Use aggregation function ('agg') and aggregation interval ('interval') to enable aggregation of the results on the database / server side. " +
  297 + "The aggregation is generally more efficient then fetching all records. \n\n"
  298 + + MARKDOWN_CODE_BLOCK_START
  299 + + TS_STRICT_DATA_EXAMPLE
  300 + + MARKDOWN_CODE_BLOCK_END
  301 + + "\n\n" + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
296 produces = MediaType.APPLICATION_JSON_VALUE) 302 produces = MediaType.APPLICATION_JSON_VALUE)
297 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 303 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
298 @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"}) 304 @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
299 @ResponseBody 305 @ResponseBody
300 public DeferredResult<ResponseEntity> getTimeseries( 306 public DeferredResult<ResponseEntity> getTimeseries(
301 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
302 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr,  
303 - @ApiParam(value = TELEMETRY_KEYS_BASE_DESCRIPTION) @RequestParam(name = "keys") String keys,  
304 - @ApiParam(value = "A long value representing the start timestamp of search time range in milliseconds.") 307 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  308 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
  309 + @ApiParam(value = TELEMETRY_KEYS_BASE_DESCRIPTION, required = true) @RequestParam(name = "keys") String keys,
  310 + @ApiParam(value = "A long value representing the start timestamp of the time range in milliseconds, UTC.")
305 @RequestParam(name = "startTs") Long startTs, 311 @RequestParam(name = "startTs") Long startTs,
306 - @ApiParam(value = "A long value representing the end timestamp of search time range in milliseconds.") 312 + @ApiParam(value = "A long value representing the end timestamp of the time range in milliseconds, UTC.")
307 @RequestParam(name = "endTs") Long endTs, 313 @RequestParam(name = "endTs") Long endTs,
308 @ApiParam(value = "A long value representing the aggregation interval range in milliseconds.") 314 @ApiParam(value = "A long value representing the aggregation interval range in milliseconds.")
309 @RequestParam(name = "interval", defaultValue = "0") Long interval, 315 @RequestParam(name = "interval", defaultValue = "0") Long interval,
@@ -329,11 +335,10 @@ public class TelemetryController extends BaseController { @@ -329,11 +335,10 @@ public class TelemetryController extends BaseController {
329 }); 335 });
330 } 336 }
331 337
332 - @ApiOperation(value = "Save or update device attributes (saveDeviceAttributes)",  
333 - notes = "Creates or updates the device attributes based on device id, specified attribute scope, " +  
334 - "and request payload that represents a JSON object with key-value format of attributes to create or update. " +  
335 - "For example, '{\"temperature\": 26}'. Key is a unique parameter and cannot be overwritten. Only value can " +  
336 - "be overwritten for the key. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 338 + @ApiOperation(value = "Save device attributes (saveDeviceAttributes)",
  339 + notes = "Creates or updates the device attributes based on device id and specified attribute scope. " +
  340 + SAVE_ATTRIBUTES_REQUEST_PAYLOAD
  341 + + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
337 produces = MediaType.APPLICATION_JSON_VALUE) 342 produces = MediaType.APPLICATION_JSON_VALUE)
338 @ApiResponses(value = { 343 @ApiResponses(value = {
339 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + 344 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK +
@@ -348,15 +353,18 @@ public class TelemetryController extends BaseController { @@ -348,15 +353,18 @@ public class TelemetryController extends BaseController {
348 @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.POST) 353 @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.POST)
349 @ResponseBody 354 @ResponseBody
350 public DeferredResult<ResponseEntity> saveDeviceAttributes( 355 public DeferredResult<ResponseEntity> saveDeviceAttributes(
351 - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable("deviceId") String deviceIdStr,  
352 - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,  
353 - @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION) @RequestBody JsonNode request) throws ThingsboardException { 356 + @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable("deviceId") String deviceIdStr,
  357 + @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
  358 + @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
354 EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); 359 EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
355 return saveAttributes(getTenantId(), entityId, scope, request); 360 return saveAttributes(getTenantId(), entityId, scope, request);
356 } 361 }
357 362
358 - @ApiOperation(value = "Save or update attributes (saveEntityAttributesV1)",  
359 - notes = SAVE_ENTITY_ATTRIBUTES_DESCRIPTION + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 363 + @ApiOperation(value = "Save entity attributes (saveEntityAttributesV1)",
  364 + notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
  365 + ENTITY_ATTRIBUTE_SCOPES +
  366 + SAVE_ATTRIBUTES_REQUEST_PAYLOAD
  367 + + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
360 produces = MediaType.APPLICATION_JSON_VALUE) 368 produces = MediaType.APPLICATION_JSON_VALUE)
361 @ApiResponses(value = { 369 @ApiResponses(value = {
362 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK), 370 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK),
@@ -368,16 +376,19 @@ public class TelemetryController extends BaseController { @@ -368,16 +376,19 @@ public class TelemetryController extends BaseController {
368 @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.POST) 376 @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.POST)
369 @ResponseBody 377 @ResponseBody
370 public DeferredResult<ResponseEntity> saveEntityAttributesV1( 378 public DeferredResult<ResponseEntity> saveEntityAttributesV1(
371 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
372 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr, 379 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  380 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
373 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope, 381 @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
374 - @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION) @RequestBody JsonNode request) throws ThingsboardException { 382 + @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
375 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); 383 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
376 return saveAttributes(getTenantId(), entityId, scope, request); 384 return saveAttributes(getTenantId(), entityId, scope, request);
377 } 385 }
378 386
379 - @ApiOperation(value = "Save or update attributes (saveEntityAttributesV2)",  
380 - notes = SAVE_ENTITY_ATTRIBUTES_DESCRIPTION + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 387 + @ApiOperation(value = "Save entity attributes (saveEntityAttributesV2)",
  388 + notes = "Creates or updates the entity attributes based on Entity Id and the specified attribute scope. " +
  389 + ENTITY_ATTRIBUTE_SCOPES +
  390 + SAVE_ATTRIBUTES_REQUEST_PAYLOAD
  391 + + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
381 produces = MediaType.APPLICATION_JSON_VALUE) 392 produces = MediaType.APPLICATION_JSON_VALUE)
382 @ApiResponses(value = { 393 @ApiResponses(value = {
383 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK), 394 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK),
@@ -389,16 +400,20 @@ public class TelemetryController extends BaseController { @@ -389,16 +400,20 @@ public class TelemetryController extends BaseController {
389 @RequestMapping(value = "/{entityType}/{entityId}/attributes/{scope}", method = RequestMethod.POST) 400 @RequestMapping(value = "/{entityType}/{entityId}/attributes/{scope}", method = RequestMethod.POST)
390 @ResponseBody 401 @ResponseBody
391 public DeferredResult<ResponseEntity> saveEntityAttributesV2( 402 public DeferredResult<ResponseEntity> saveEntityAttributesV2(
392 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
393 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr,  
394 - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,  
395 - @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION) @RequestBody JsonNode request) throws ThingsboardException { 403 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  404 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
  405 + @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
  406 + @ApiParam(value = ATTRIBUTES_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody JsonNode request) throws ThingsboardException {
396 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); 407 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
397 return saveAttributes(getTenantId(), entityId, scope, request); 408 return saveAttributes(getTenantId(), entityId, scope, request);
398 } 409 }
399 410
400 - @ApiOperation(value = "Save or update telemetry (saveEntityTelemetry)",  
401 - notes = SAVE_ENTITY_TIMESERIES_DESCRIPTION + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 411 +
  412 + @ApiOperation(value = "Save or update time-series data (saveEntityTelemetry)",
  413 + notes = "Creates or updates the entity time-series data based on the Entity Id and request payload." +
  414 + SAVE_TIMESERIES_REQUEST_PAYLOAD +
  415 + "\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. "
  416 + + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
402 produces = MediaType.APPLICATION_JSON_VALUE) 417 produces = MediaType.APPLICATION_JSON_VALUE)
403 @ApiResponses(value = { 418 @ApiResponses(value = {
404 @ApiResponse(code = 200, message = SAVE_ENTITY_TIMESERIES_STATUS_OK), 419 @ApiResponse(code = 200, message = SAVE_ENTITY_TIMESERIES_STATUS_OK),
@@ -410,16 +425,19 @@ public class TelemetryController extends BaseController { @@ -410,16 +425,19 @@ public class TelemetryController extends BaseController {
410 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}", method = RequestMethod.POST) 425 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}", method = RequestMethod.POST)
411 @ResponseBody 426 @ResponseBody
412 public DeferredResult<ResponseEntity> saveEntityTelemetry( 427 public DeferredResult<ResponseEntity> saveEntityTelemetry(
413 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
414 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr,  
415 - @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION) @PathVariable("scope") String scope,  
416 - @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION) @RequestBody String requestBody) throws ThingsboardException { 428 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  429 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
  430 + @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope,
  431 + @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
417 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); 432 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
418 return saveTelemetry(getTenantId(), entityId, requestBody, 0L); 433 return saveTelemetry(getTenantId(), entityId, requestBody, 0L);
419 } 434 }
420 435
421 - @ApiOperation(value = "Save or update telemetry with TTL (saveEntityTelemetryWithTTL)",  
422 - notes = SAVE_ENTITY_TIMESERIES_DESCRIPTION + "The ttl parameter used only in case of Cassandra DB use for timeseries data storage. " 436 + @ApiOperation(value = "Save or update time-series data with TTL (saveEntityTelemetryWithTTL)",
  437 + notes = "Creates or updates the entity time-series data based on the Entity Id and request payload." +
  438 + SAVE_TIMESERIES_REQUEST_PAYLOAD +
  439 + "\n\n The scope parameter is not used in the API call implementation but should be specified whatever value because it is used as a path variable. "
  440 + + "\n\nThe ttl parameter takes affect only in case of Cassandra DB."
423 + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 441 + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
424 produces = MediaType.APPLICATION_JSON_VALUE) 442 produces = MediaType.APPLICATION_JSON_VALUE)
425 @ApiResponses(value = { 443 @ApiResponses(value = {
@@ -432,19 +450,21 @@ public class TelemetryController extends BaseController { @@ -432,19 +450,21 @@ public class TelemetryController extends BaseController {
432 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}/{ttl}", method = RequestMethod.POST) 450 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}/{ttl}", method = RequestMethod.POST)
433 @ResponseBody 451 @ResponseBody
434 public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL( 452 public DeferredResult<ResponseEntity> saveEntityTelemetryWithTTL(
435 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
436 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr,  
437 - @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION) @PathVariable("scope") String scope,  
438 - @ApiParam(value = "A long value representing TTL (Time to Live) parameter.") @PathVariable("ttl") Long ttl,  
439 - @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION) @RequestBody String requestBody) throws ThingsboardException { 453 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  454 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
  455 + @ApiParam(value = TELEMETRY_SCOPE_DESCRIPTION, required = true, allowableValues = "ANY") @PathVariable("scope") String scope,
  456 + @ApiParam(value = "A long value representing TTL (Time to Live) parameter.", required = true) @PathVariable("ttl") Long ttl,
  457 + @ApiParam(value = TELEMETRY_JSON_REQUEST_DESCRIPTION, required = true) @RequestBody String requestBody) throws ThingsboardException {
440 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); 458 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
441 return saveTelemetry(getTenantId(), entityId, requestBody, ttl); 459 return saveTelemetry(getTenantId(), entityId, requestBody, ttl);
442 } 460 }
443 461
444 - @ApiOperation(value = "Delete entity timeseries (deleteEntityTimeseries)",  
445 - notes = "Delete timeseries for selected entity based on entity id, entity type, keys " +  
446 - "and removal time range. To delete all data for keys parameter 'deleteAllDataForKeys' should be set to true, " +  
447 - "otherwise, will be deleted data that is in range of the selected time interval. " + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 462 + @ApiOperation(value = "Delete entity time-series data (deleteEntityTimeseries)",
  463 + notes = "Delete time-series for selected entity based on entity id, entity type and keys." +
  464 + " Use 'deleteAllDataForKeys' to delete all time-series data." +
  465 + " Use 'startTs' and 'endTs' to specify time-range instead. " +
  466 + " Use 'rewriteLatestIfDeleted' to rewrite latest value (stored in separate table for performance) after deletion of the time range. " +
  467 + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
448 produces = MediaType.APPLICATION_JSON_VALUE) 468 produces = MediaType.APPLICATION_JSON_VALUE)
449 @ApiResponses(value = { 469 @ApiResponses(value = {
450 @ApiResponse(code = 200, message = "Timeseries for the selected keys in the request was removed. " + 470 @ApiResponse(code = 200, message = "Timeseries for the selected keys in the request was removed. " +
@@ -458,9 +478,9 @@ public class TelemetryController extends BaseController { @@ -458,9 +478,9 @@ public class TelemetryController extends BaseController {
458 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE) 478 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE)
459 @ResponseBody 479 @ResponseBody
460 public DeferredResult<ResponseEntity> deleteEntityTimeseries( 480 public DeferredResult<ResponseEntity> deleteEntityTimeseries(
461 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
462 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr,  
463 - @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys") String keysStr, 481 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  482 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
  483 + @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr,
464 @ApiParam(value = "A boolean value to specify if should be deleted all data for selected keys or only data that are in the selected time range.") 484 @ApiParam(value = "A boolean value to specify if should be deleted all data for selected keys or only data that are in the selected time range.")
465 @RequestParam(name = "deleteAllDataForKeys", defaultValue = "false") boolean deleteAllDataForKeys, 485 @RequestParam(name = "deleteAllDataForKeys", defaultValue = "false") boolean deleteAllDataForKeys,
466 @ApiParam(value = "A long value representing the start timestamp of removal time range in milliseconds.") 486 @ApiParam(value = "A long value representing the start timestamp of removal time range in milliseconds.")
@@ -501,7 +521,7 @@ public class TelemetryController extends BaseController { @@ -501,7 +521,7 @@ public class TelemetryController extends BaseController {
501 deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted)); 521 deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted));
502 } 522 }
503 ListenableFuture<List<Void>> future = tsService.remove(user.getTenantId(), entityId, deleteTsKvQueries); 523 ListenableFuture<List<Void>> future = tsService.remove(user.getTenantId(), entityId, deleteTsKvQueries);
504 - Futures.addCallback(future, new FutureCallback<List<Void>>() { 524 + Futures.addCallback(future, new FutureCallback<>() {
505 @Override 525 @Override
506 public void onSuccess(@Nullable List<Void> tmp) { 526 public void onSuccess(@Nullable List<Void> tmp) {
507 logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null); 527 logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
@@ -517,9 +537,9 @@ public class TelemetryController extends BaseController { @@ -517,9 +537,9 @@ public class TelemetryController extends BaseController {
517 }); 537 });
518 } 538 }
519 539
520 - @ApiOperation(value = "Delete device attributes (deleteEntityAttributes)",  
521 - notes = "Delete device attributes from the specified attributes scope based on device id and a list of keys to delete. " +  
522 - "Selected keys will be deleted only if there are exist in the specified attribute scope. Referencing a non-existing device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 540 + @ApiOperation(value = "Delete device attributes (deleteDeviceAttributes)",
  541 + notes = "Delete device attributes using provided Device Id, scope and a list of keys. " +
  542 + "Referencing a non-existing Device Id will cause an error" + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
523 produces = MediaType.APPLICATION_JSON_VALUE) 543 produces = MediaType.APPLICATION_JSON_VALUE)
524 @ApiResponses(value = { 544 @ApiResponses(value = {
525 @ApiResponse(code = 200, message = "Device attributes was removed for the selected keys in the request. " + 545 @ApiResponse(code = 200, message = "Device attributes was removed for the selected keys in the request. " +
@@ -532,17 +552,17 @@ public class TelemetryController extends BaseController { @@ -532,17 +552,17 @@ public class TelemetryController extends BaseController {
532 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") 552 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
533 @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE) 553 @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE)
534 @ResponseBody 554 @ResponseBody
535 - public DeferredResult<ResponseEntity> deleteEntityAttributes(  
536 - @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION) @PathVariable("deviceId") String deviceIdStr,  
537 - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,  
538 - @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { 555 + public DeferredResult<ResponseEntity> deleteDeviceAttributes(
  556 + @ApiParam(value = DEVICE_ID_PARAM_DESCRIPTION, required = true) @PathVariable(DEVICE_ID) String deviceIdStr,
  557 + @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES, required = true) @PathVariable("scope") String scope,
  558 + @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
539 EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr); 559 EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
540 return deleteAttributes(entityId, scope, keysStr); 560 return deleteAttributes(entityId, scope, keysStr);
541 } 561 }
542 562
543 @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)", 563 @ApiOperation(value = "Delete entity attributes (deleteEntityAttributes)",
544 - notes = "Delete entity attributes from the specified attributes scope based on entity id, entity type and a list of keys to delete. " +  
545 - "Selected keys will be deleted only if there are exist in the specified attribute scope." + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH, 564 + notes = "Delete entity attributes using provided Entity Id, scope and a list of keys. " +
  565 + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
546 produces = MediaType.APPLICATION_JSON_VALUE) 566 produces = MediaType.APPLICATION_JSON_VALUE)
547 @ApiResponses(value = { 567 @ApiResponses(value = {
548 @ApiResponse(code = 200, message = "Entity attributes was removed for the selected keys in the request. " + 568 @ApiResponse(code = 200, message = "Entity attributes was removed for the selected keys in the request. " +
@@ -556,10 +576,10 @@ public class TelemetryController extends BaseController { @@ -556,10 +576,10 @@ public class TelemetryController extends BaseController {
556 @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.DELETE) 576 @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.DELETE)
557 @ResponseBody 577 @ResponseBody
558 public DeferredResult<ResponseEntity> deleteEntityAttributes( 578 public DeferredResult<ResponseEntity> deleteEntityAttributes(
559 - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION) @PathVariable("entityType") String entityType,  
560 - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION) @PathVariable("entityId") String entityIdStr,  
561 - @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,  
562 - @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys") String keysStr) throws ThingsboardException { 579 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE") @PathVariable("entityType") String entityType,
  580 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true) @PathVariable("entityId") String entityIdStr,
  581 + @ApiParam(value = ATTRIBUTES_SCOPE_DESCRIPTION, required = true, allowableValues = ATTRIBUTES_SCOPE_ALLOWED_VALUES) @PathVariable("scope") String scope,
  582 + @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION, required = true) @RequestParam(name = "keys") String keysStr) throws ThingsboardException {
563 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr); 583 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
564 return deleteAttributes(entityId, scope, keysStr); 584 return deleteAttributes(entityId, scope, keysStr);
565 } 585 }
@@ -732,7 +752,7 @@ public class TelemetryController extends BaseController { @@ -732,7 +752,7 @@ public class TelemetryController extends BaseController {
732 } 752 }
733 753
734 private FutureCallback<List<TsKvEntry>> getTsKeysToResponseCallback(final DeferredResult<ResponseEntity> response) { 754 private FutureCallback<List<TsKvEntry>> getTsKeysToResponseCallback(final DeferredResult<ResponseEntity> response) {
735 - return new FutureCallback<List<TsKvEntry>>() { 755 + return new FutureCallback<>() {
736 @Override 756 @Override
737 public void onSuccess(List<TsKvEntry> values) { 757 public void onSuccess(List<TsKvEntry> values) {
738 List<String> keys = values.stream().map(KvEntry::getKey).collect(Collectors.toList()); 758 List<String> keys = values.stream().map(KvEntry::getKey).collect(Collectors.toList());
@@ -767,7 +787,7 @@ public class TelemetryController extends BaseController { @@ -767,7 +787,7 @@ public class TelemetryController extends BaseController {
767 private FutureCallback<List<AttributeKvEntry>> getAttributeValuesToResponseCallback(final DeferredResult<ResponseEntity> response, 787 private FutureCallback<List<AttributeKvEntry>> getAttributeValuesToResponseCallback(final DeferredResult<ResponseEntity> response,
768 final SecurityUser user, final String scope, 788 final SecurityUser user, final String scope,
769 final EntityId entityId, final List<String> keyList) { 789 final EntityId entityId, final List<String> keyList) {
770 - return new FutureCallback<List<AttributeKvEntry>>() { 790 + return new FutureCallback<>() {
771 @Override 791 @Override
772 public void onSuccess(List<AttributeKvEntry> attributes) { 792 public void onSuccess(List<AttributeKvEntry> attributes) {
773 List<AttributeData> values = attributes.stream().map(attribute -> 793 List<AttributeData> values = attributes.stream().map(attribute ->
@@ -787,7 +807,7 @@ public class TelemetryController extends BaseController { @@ -787,7 +807,7 @@ public class TelemetryController extends BaseController {
787 } 807 }
788 808
789 private FutureCallback<List<TsKvEntry>> getTsKvListCallback(final DeferredResult<ResponseEntity> response, Boolean useStrictDataTypes) { 809 private FutureCallback<List<TsKvEntry>> getTsKvListCallback(final DeferredResult<ResponseEntity> response, Boolean useStrictDataTypes) {
790 - return new FutureCallback<List<TsKvEntry>>() { 810 + return new FutureCallback<>() {
791 @Override 811 @Override
792 public void onSuccess(List<TsKvEntry> data) { 812 public void onSuccess(List<TsKvEntry> data) {
793 Map<String, List<TsData>> result = new LinkedHashMap<>(); 813 Map<String, List<TsData>> result = new LinkedHashMap<>();
@@ -907,16 +927,16 @@ public class TelemetryController extends BaseController { @@ -907,16 +927,16 @@ public class TelemetryController extends BaseController {
907 927
908 private String toJsonStr(JsonNode value) { 928 private String toJsonStr(JsonNode value) {
909 try { 929 try {
910 - return mapper.writeValueAsString(value);  
911 - } catch (JsonProcessingException e) { 930 + return JacksonUtil.toString(value);
  931 + } catch (IllegalArgumentException e) {
912 throw new JsonParseException("Can't parse jsonValue: " + value, e); 932 throw new JsonParseException("Can't parse jsonValue: " + value, e);
913 } 933 }
914 } 934 }
915 935
916 private JsonNode toJsonNode(String value) { 936 private JsonNode toJsonNode(String value) {
917 try { 937 try {
918 - return mapper.readTree(value);  
919 - } catch (IOException e) { 938 + return JacksonUtil.toJsonNode(value);
  939 + } catch (IllegalArgumentException e) {
920 throw new JsonParseException("Can't parse jsonValue: " + value, e); 940 throw new JsonParseException("Can't parse jsonValue: " + value, e);
921 } 941 }
922 } 942 }