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 232 @RequestMapping(value = "/alarm/{entityType}/{entityId}", method = RequestMethod.GET)
233 233 @ResponseBody
234 234 public PageData<AlarmInfo> getAlarms(
235   - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION)
  235 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE")
236 236 @PathVariable(ENTITY_TYPE) String strEntityType,
237   - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION)
  237 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
238 238 @PathVariable(ENTITY_ID) String strEntityId,
239 239 @ApiParam(value = ALARM_QUERY_SEARCH_STATUS_DESCRIPTION, allowableValues = ALARM_QUERY_SEARCH_STATUS_ALLOWABLE_VALUES)
240 240 @RequestParam(required = false) String searchStatus,
... ... @@ -333,7 +333,7 @@ public class AlarmController extends BaseController {
333 333 @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET)
334 334 @ResponseBody
335 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 337 @PathVariable(ENTITY_TYPE) String strEntityType,
338 338 @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
339 339 @PathVariable(ENTITY_ID) String strEntityId,
... ...
... ... @@ -156,13 +156,13 @@ public class AuditLogController extends BaseController {
156 156 @RequestMapping(value = "/audit/logs/entity/{entityType}/{entityId}", params = {"pageSize", "page"}, method = RequestMethod.GET)
157 157 @ResponseBody
158 158 public PageData<AuditLog> getAuditLogsByEntityId(
159   - @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION)
  159 + @ApiParam(value = ENTITY_TYPE_PARAM_DESCRIPTION, required = true, defaultValue = "DEVICE")
160 160 @PathVariable("entityType") String strEntityType,
161   - @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION)
  161 + @ApiParam(value = ENTITY_ID_PARAM_DESCRIPTION, required = true)
162 162 @PathVariable("entityId") String strEntityId,
163   - @ApiParam(value = PAGE_SIZE_DESCRIPTION)
  163 + @ApiParam(value = PAGE_SIZE_DESCRIPTION, required = true)
164 164 @RequestParam int pageSize,
165   - @ApiParam(value = PAGE_NUMBER_DESCRIPTION)
  165 + @ApiParam(value = PAGE_NUMBER_DESCRIPTION, required = true)
166 166 @RequestParam int page,
167 167 @ApiParam(value = AUDIT_LOG_TEXT_SEARCH_DESCRIPTION)
168 168 @RequestParam(required = false) String textSearch,
... ...
... ... @@ -1409,5 +1409,116 @@ public class ControllerConstants {
1409 1409 "As a Tenant Administrator you are able to create multiple EVs per Device or Asset and assign them to different Customers. ";
1410 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 218 @RequestBody SaveDeviceWithCredentialsRequest deviceAndCredentials) throws ThingsboardException {
219 219 Device device = checkNotNull(deviceAndCredentials.getDevice());
220 220 DeviceCredentials credentials = checkNotNull(deviceAndCredentials.getCredentials());
  221 + boolean created = device.getId() == null;
221 222 try {
222 223 device.setTenantId(getCurrentUser().getTenantId());
223 224 checkEntity(device.getId(), device, Resource.DEVICE);
... ... @@ -231,7 +232,7 @@ public class DeviceController extends BaseController {
231 232 return savedDevice;
232 233 } catch (Exception e) {
233 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 236 throw handleException(e);
236 237 }
237 238 }
... ...
... ... @@ -15,9 +15,7 @@
15 15 */
16 16 package org.thingsboard.server.controller;
17 17
18   -import com.fasterxml.jackson.core.JsonProcessingException;
19 18 import com.fasterxml.jackson.databind.JsonNode;
20   -import com.fasterxml.jackson.databind.ObjectMapper;
21 19 import com.google.common.base.Function;
22 20 import com.google.common.util.concurrent.FutureCallback;
23 21 import com.google.common.util.concurrent.Futures;
... ... @@ -46,6 +44,7 @@ import org.springframework.web.bind.annotation.RequestParam;
46 44 import org.springframework.web.bind.annotation.ResponseBody;
47 45 import org.springframework.web.bind.annotation.RestController;
48 46 import org.springframework.web.context.request.async.DeferredResult;
  47 +import org.thingsboard.common.util.JacksonUtil;
49 48 import org.thingsboard.common.util.ThingsBoardThreadFactory;
50 49 import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
51 50 import org.thingsboard.server.common.data.DataConstants;
... ... @@ -90,7 +89,6 @@ import org.thingsboard.server.service.telemetry.exception.UncheckedApiException;
90 89 import javax.annotation.Nullable;
91 90 import javax.annotation.PostConstruct;
92 91 import javax.annotation.PreDestroy;
93   -import java.io.IOException;
94 92 import java.util.ArrayList;
95 93 import java.util.Arrays;
96 94 import java.util.HashSet;
... ... @@ -103,12 +101,41 @@ import java.util.concurrent.Executors;
103 101 import java.util.concurrent.TimeUnit;
104 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 110 import static org.thingsboard.server.controller.ControllerConstants.DEVICE_ID_PARAM_DESCRIPTION;
  111 +import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ATTRIBUTE_SCOPES;
107 112 import static org.thingsboard.server.controller.ControllerConstants.ENTITY_ID_PARAM_DESCRIPTION;
108 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 130 import static org.thingsboard.server.controller.ControllerConstants.SORT_ORDER_ALLOWABLE_VALUES;
110 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 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 147 @Slf4j
121 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 150 @Autowired
166 151 private TimeseriesService tsService;
167 152
... ... @@ -173,8 +158,6 @@ public class TelemetryController extends BaseController {
173 158
174 159 private ExecutorService executor;
175 160
176   - private static final ObjectMapper mapper = new ObjectMapper();
177   -
178 161 @PostConstruct
179 162 public void initExecutor() {
180 163 executor = Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName("telemetry-controller"));
... ... @@ -188,96 +171,117 @@ public class TelemetryController extends BaseController {
188 171 }
189 172
190 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 180 produces = MediaType.APPLICATION_JSON_VALUE)
196 181 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
197 182 @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes", method = RequestMethod.GET)
198 183 @ResponseBody
199 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 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 196 produces = MediaType.APPLICATION_JSON_VALUE)
210 197 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
211 198 @RequestMapping(value = "/{entityType}/{entityId}/keys/attributes/{scope}", method = RequestMethod.GET)
212 199 @ResponseBody
213 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 204 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
218 205 (result, tenantId, entityId) -> getAttributeKeysCallback(result, tenantId, entityId, scope));
219 206 }
220 207
221 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 215 produces = MediaType.APPLICATION_JSON_VALUE)
225 216 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
226 217 @RequestMapping(value = "/{entityType}/{entityId}/values/attributes", method = RequestMethod.GET)
227 218 @ResponseBody
228 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 222 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
232 223 SecurityUser user = getCurrentUser();
233 224 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
234 225 (result, tenantId, entityId) -> getAttributeValuesCallback(result, user, entityId, null, keysStr));
235 226 }
236 227
  228 +
237 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 238 produces = MediaType.APPLICATION_JSON_VALUE)
243 239 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
244 240 @RequestMapping(value = "/{entityType}/{entityId}/values/attributes/{scope}", method = RequestMethod.GET)
245 241 @ResponseBody
246 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 246 @ApiParam(value = ATTRIBUTES_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr) throws ThingsboardException {
251 247 SecurityUser user = getCurrentUser();
252 248 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_ATTRIBUTES, entityType, entityIdStr,
253 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 255 produces = MediaType.APPLICATION_JSON_VALUE)
260 256 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
261 257 @RequestMapping(value = "/{entityType}/{entityId}/keys/timeseries", method = RequestMethod.GET)
262 258 @ResponseBody
263 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 262 return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.READ_TELEMETRY, entityType, entityIdStr,
267 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 278 produces = MediaType.APPLICATION_JSON_VALUE)
275 279 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
276 280 @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET)
277 281 @ResponseBody
278 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 285 @ApiParam(value = TELEMETRY_KEYS_DESCRIPTION) @RequestParam(name = "keys", required = false) String keysStr,
282 286 @ApiParam(value = STRICT_DATA_TYPES_DESCRIPTION)
283 287 @RequestParam(name = "useStrictDataTypes", required = false, defaultValue = "false") Boolean useStrictDataTypes) throws ThingsboardException {
... ... @@ -286,24 +290,26 @@ public class TelemetryController extends BaseController {
286 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 302 produces = MediaType.APPLICATION_JSON_VALUE)
297 303 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
298 304 @RequestMapping(value = "/{entityType}/{entityId}/values/timeseries", method = RequestMethod.GET, params = {"keys", "startTs", "endTs"})
299 305 @ResponseBody
300 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 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 313 @RequestParam(name = "endTs") Long endTs,
308 314 @ApiParam(value = "A long value representing the aggregation interval range in milliseconds.")
309 315 @RequestParam(name = "interval", defaultValue = "0") Long interval,
... ... @@ -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 342 produces = MediaType.APPLICATION_JSON_VALUE)
338 343 @ApiResponses(value = {
339 344 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK +
... ... @@ -348,15 +353,18 @@ public class TelemetryController extends BaseController {
348 353 @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.POST)
349 354 @ResponseBody
350 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 359 EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
355 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 368 produces = MediaType.APPLICATION_JSON_VALUE)
361 369 @ApiResponses(value = {
362 370 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK),
... ... @@ -368,16 +376,19 @@ public class TelemetryController extends BaseController {
368 376 @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.POST)
369 377 @ResponseBody
370 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 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 383 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
376 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 392 produces = MediaType.APPLICATION_JSON_VALUE)
382 393 @ApiResponses(value = {
383 394 @ApiResponse(code = 200, message = SAVE_ATTIRIBUTES_STATUS_OK + SAVE_ENTITY_ATTRIBUTES_STATUS_OK),
... ... @@ -389,16 +400,20 @@ public class TelemetryController extends BaseController {
389 400 @RequestMapping(value = "/{entityType}/{entityId}/attributes/{scope}", method = RequestMethod.POST)
390 401 @ResponseBody
391 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 407 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
397 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 417 produces = MediaType.APPLICATION_JSON_VALUE)
403 418 @ApiResponses(value = {
404 419 @ApiResponse(code = 200, message = SAVE_ENTITY_TIMESERIES_STATUS_OK),
... ... @@ -410,16 +425,19 @@ public class TelemetryController extends BaseController {
410 425 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}", method = RequestMethod.POST)
411 426 @ResponseBody
412 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 432 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
418 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 441 + INVALID_ENTITY_ID_OR_ENTITY_TYPE_DESCRIPTION + TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH,
424 442 produces = MediaType.APPLICATION_JSON_VALUE)
425 443 @ApiResponses(value = {
... ... @@ -432,19 +450,21 @@ public class TelemetryController extends BaseController {
432 450 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/{scope}/{ttl}", method = RequestMethod.POST)
433 451 @ResponseBody
434 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 458 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
441 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 468 produces = MediaType.APPLICATION_JSON_VALUE)
449 469 @ApiResponses(value = {
450 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 478 @RequestMapping(value = "/{entityType}/{entityId}/timeseries/delete", method = RequestMethod.DELETE)
459 479 @ResponseBody
460 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 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 485 @RequestParam(name = "deleteAllDataForKeys", defaultValue = "false") boolean deleteAllDataForKeys,
466 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 521 deleteTsKvQueries.add(new BaseDeleteTsKvQuery(key, deleteFromTs, deleteToTs, rewriteLatestIfDeleted));
502 522 }
503 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 525 @Override
506 526 public void onSuccess(@Nullable List<Void> tmp) {
507 527 logTimeseriesDeleted(user, entityId, keys, deleteFromTs, deleteToTs, null);
... ... @@ -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 543 produces = MediaType.APPLICATION_JSON_VALUE)
524 544 @ApiResponses(value = {
525 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 552 @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
533 553 @RequestMapping(value = "/{deviceId}/{scope}", method = RequestMethod.DELETE)
534 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 559 EntityId entityId = EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE, deviceIdStr);
540 560 return deleteAttributes(entityId, scope, keysStr);
541 561 }
542 562
543 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 566 produces = MediaType.APPLICATION_JSON_VALUE)
547 567 @ApiResponses(value = {
548 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 576 @RequestMapping(value = "/{entityType}/{entityId}/{scope}", method = RequestMethod.DELETE)
557 577 @ResponseBody
558 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 583 EntityId entityId = EntityIdFactory.getByTypeAndId(entityType, entityIdStr);
564 584 return deleteAttributes(entityId, scope, keysStr);
565 585 }
... ... @@ -732,7 +752,7 @@ public class TelemetryController extends BaseController {
732 752 }
733 753
734 754 private FutureCallback<List<TsKvEntry>> getTsKeysToResponseCallback(final DeferredResult<ResponseEntity> response) {
735   - return new FutureCallback<List<TsKvEntry>>() {
  755 + return new FutureCallback<>() {
736 756 @Override
737 757 public void onSuccess(List<TsKvEntry> values) {
738 758 List<String> keys = values.stream().map(KvEntry::getKey).collect(Collectors.toList());
... ... @@ -767,7 +787,7 @@ public class TelemetryController extends BaseController {
767 787 private FutureCallback<List<AttributeKvEntry>> getAttributeValuesToResponseCallback(final DeferredResult<ResponseEntity> response,
768 788 final SecurityUser user, final String scope,
769 789 final EntityId entityId, final List<String> keyList) {
770   - return new FutureCallback<List<AttributeKvEntry>>() {
  790 + return new FutureCallback<>() {
771 791 @Override
772 792 public void onSuccess(List<AttributeKvEntry> attributes) {
773 793 List<AttributeData> values = attributes.stream().map(attribute ->
... ... @@ -787,7 +807,7 @@ public class TelemetryController extends BaseController {
787 807 }
788 808
789 809 private FutureCallback<List<TsKvEntry>> getTsKvListCallback(final DeferredResult<ResponseEntity> response, Boolean useStrictDataTypes) {
790   - return new FutureCallback<List<TsKvEntry>>() {
  810 + return new FutureCallback<>() {
791 811 @Override
792 812 public void onSuccess(List<TsKvEntry> data) {
793 813 Map<String, List<TsData>> result = new LinkedHashMap<>();
... ... @@ -907,16 +927,16 @@ public class TelemetryController extends BaseController {
907 927
908 928 private String toJsonStr(JsonNode value) {
909 929 try {
910   - return mapper.writeValueAsString(value);
911   - } catch (JsonProcessingException e) {
  930 + return JacksonUtil.toString(value);
  931 + } catch (IllegalArgumentException e) {
912 932 throw new JsonParseException("Can't parse jsonValue: " + value, e);
913 933 }
914 934 }
915 935
916 936 private JsonNode toJsonNode(String value) {
917 937 try {
918   - return mapper.readTree(value);
919   - } catch (IOException e) {
  938 + return JacksonUtil.toJsonNode(value);
  939 + } catch (IllegalArgumentException e) {
920 940 throw new JsonParseException("Can't parse jsonValue: " + value, e);
921 941 }
922 942 }
... ...